fixed markdown format
This commit is contained in:
16
1.1.md
16
1.1.md
@@ -1,6 +1,6 @@
|
||||
# 1.1 Go 安装
|
||||
|
||||
##三种Go安装方式
|
||||
## 三种Go安装方式
|
||||
你能通过多种方式来安装Go,不同的人喜欢不同的安装方式,这里我们介绍目前常见的三种安装方式,下面是每种方式的大概介绍:
|
||||
|
||||
- 源码安装:这是一种标准的软件安装方式,对于开发者来说,从源码安装将会是最熟悉的、特别对于经常使用unix类系统的用户。
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
最后,如果你想要在自己的同一个系统里面安装多个版本的Go,你可以在第三方工具里面详细参考gvm工具,这是目前这方面做的最好的工具,除非你知道怎么处理。
|
||||
|
||||
###Go源码安装
|
||||
### Go源码安装
|
||||
在Go的源代码中,有些部分是用C和汇编写的,因此你要想从源码安装,就必须安装C的编译工具。
|
||||
|
||||
在Mac系统中,只要你安装了Xcode,就已经包含了相应的编译工具。
|
||||
@@ -43,9 +43,9 @@ Go使用[Mercurial](http://mercurial.selenic.com/downloads/)进行版本管理
|
||||
如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。
|
||||
|
||||
|
||||
##Go标准包安装
|
||||
## Go标准包安装
|
||||
|
||||
###如何判断自己的操作系统是32位还是64位?
|
||||
### 如何判断自己的操作系统是32位还是64位?
|
||||
|
||||
我们接下来的Go安装需要判断操作系统的位数,所以这小节我们先确定自己的系统类型。
|
||||
|
||||
@@ -98,8 +98,8 @@ Linux系统用户可通过在Terminal中执行命令`uname -a`来查看系统信
|
||||
|
||||
如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。
|
||||
|
||||
##第三方工具安装
|
||||
###GVM
|
||||
## 第三方工具安装
|
||||
### GVM
|
||||
gvm是使用第三方开发的能够管理多个版本工具,类似ruby里面的rvm工具。使用起来相当的方便,安装gvm使用如下命令:
|
||||
|
||||
bash < <(curl -s https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer)
|
||||
@@ -111,14 +111,14 @@ gvm是使用第三方开发的能够管理多个版本工具,类似ruby里面
|
||||
|
||||
执行上面的命令之后GOPATH、GOROOT等环境变量自动设置好了,就可以直接使用了。
|
||||
|
||||
###apt-get
|
||||
### apt-get
|
||||
Ubuntu是目前使用最多的Linux桌面系统,使用`apt-get`命令来管理软件包,我们可以通过下面的命令来安装golang:
|
||||
|
||||
sudo add-apt-repository ppa:gophers/go
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-stable
|
||||
|
||||
###homebrew
|
||||
### homebrew
|
||||
homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已经支持golang,可以通过命令一键安装go:
|
||||
|
||||
brew install go
|
||||
|
||||
18
1.3.md
18
1.3.md
@@ -1,6 +1,6 @@
|
||||
# 1.3 Go 命令
|
||||
|
||||
##Go 命令
|
||||
## Go 命令
|
||||
|
||||
Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。
|
||||
|
||||
##go build
|
||||
## go build
|
||||
|
||||
这个命令主要用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
`go build`的时候会选择性地编译以系统名结尾的文件(linux、darwin、windows、freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。
|
||||
|
||||
##go clean
|
||||
## go clean
|
||||
|
||||
这个命令是用来移除当前源码包里面编译的文件的。这些文件包括
|
||||
|
||||
@@ -50,11 +50,11 @@
|
||||
|
||||
我一般都是利用这个命令进行清除编译文件,然后github递交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要
|
||||
|
||||
##go fmt
|
||||
## go fmt
|
||||
|
||||
有过C/C++经验的读者会知道,一些人经常为代码采取K&R风格还是ANSI风格而争论不休。在go中,代码则有标准的风格。由于之前已经有的一些习惯或其它的原因我们常将代码写成ANSI风格或者其它更合适自己的格式,这将为人们在阅读别人的代码时添加不必要的负担,所以go强制了代码格式(比如左大括号必须放在行尾),不按照此格式的代码将不能编译通过,为了减少浪费在排版上的时间,go工具集中提供了一个`go fmt`命令 它可以帮你格式化你写好的代码文件,使你写代码的时候不需要关心格式,你只需要在写完之后执行`go fmt <文件名>.go`,你的代码就被修改成了标准格式,但是我平常很少用到这个命令,因为开发工具里面一般都带了保存时候自动格式化功能,这个功能其实在底层就是调用了`go fmt`。接下来的一节我将讲述两个工具,这两个工具都自带了保存文件时自动化`go fmt`功能。
|
||||
|
||||
##go get
|
||||
## go get
|
||||
|
||||
这个命令是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行`go install`。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下:
|
||||
|
||||
@@ -65,11 +65,11 @@
|
||||
|
||||
所以为了`go get` 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH中。其实`go get`支持自定义域名的功能,具体参见`go help remote`。
|
||||
|
||||
##go install
|
||||
## go install
|
||||
|
||||
这个命令在内部实际上分成了两步操作:第一步是`go build`,第二步会把编译好的东西move到`$GOPATH/pkg`或者`$GOPATH/bin`。
|
||||
|
||||
##go test
|
||||
## go test
|
||||
|
||||
执行这个命令,会自动读取源码目录下面名为`*_test.go`的文件,生成并运行测试用的可执行文件。输出的信息类似
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
默认的情况下,不需要任何的参数,它会自动把你源码包下面所有test文件测试完毕,当然你也可以带上参数,详情请参考`go help testflag`
|
||||
|
||||
##go doc
|
||||
## go doc
|
||||
|
||||
很多人说go不需要任何的第三方文档,例如chm手册之类的(其实我已经做了一个了,[chm手册](https://github.com/astaxie/godoc)),因为它内部就有一个很强大的文档工具。
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
通过命令在命令行执行 godoc -http=:端口号 比如`godoc -http=:8080`。然后在浏览器中打开`127.0.0.1:8080`,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地`GOPATH`中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。
|
||||
|
||||
##其它命令
|
||||
## 其它命令
|
||||
|
||||
go还提供了其它很多的工具,例如下面的这些工具
|
||||
|
||||
|
||||
6
1.4.md
6
1.4.md
@@ -1,8 +1,8 @@
|
||||
#1.4 Go开发工具
|
||||
# 1.4 Go开发工具
|
||||
|
||||
本节我将介绍几个开发工具,它们都具有自动化提示,自动化fmt功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。
|
||||
|
||||
##LiteIDE
|
||||
## LiteIDE
|
||||
|
||||
LiteIDE是一款专门为Go语言开发的集成开发环境(IDE),由visualfc编写。支持项目管理、集成构建、GDB调试、语法高亮、自动补全、大纲显示等功能。下载地址: [http://code.google.com/p/golangide/downloads/list](http://code.google.com/p/golangide/downloads/list),根据自己的系统下载相应的发行版本。Windows和Ubuntu系统可直接打开bin下面的liteide;Mac则需通过LaunchPad打开LiteIDE.app。
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
代码补全需要安装gocode,目前LiteIDE的自动化提示只支持本文件中函数的提示,还没有做到整个项目中函数的提示。
|
||||
|
||||
|
||||
##Sublime Text
|
||||
## Sublime Text
|
||||
|
||||
这里将介绍Sublime Text 2(以下简称Sublime)+GoSublime+gocode+MarGo的组合,那么为什么选择这个组合呢?
|
||||
|
||||
|
||||
2
1.5.md
2
1.5.md
@@ -1,4 +1,4 @@
|
||||
#总结
|
||||
# 总结
|
||||
|
||||
这一章中我们主要介绍了如何安装Go,以及如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后如何创建项目,项目创建之后如何编译、如何安装,接着介绍了一些Go的常用命令工具,最后介绍了Go的开发工具,希望能够通过有利的工具快速的开发Go应用。
|
||||
|
||||
|
||||
12
10.1.md
12
10.1.md
@@ -1,5 +1,5 @@
|
||||
# 10.1 设置默认地区
|
||||
##什么是Locale
|
||||
## 什么是Locale
|
||||
Locale是一组描述世界上某一特定区域文本格式和语言习惯的设置的集合。locale名通常由三个部分组成:第一部分,是一个强制性的,表示语言的缩写,例如"en"表示英文或"zh"表示中文。第二部分,跟在一个下划线之后,是一个可选的国家说明符,用于区分讲同一种语言的不同国家,例如"en_US"表示美国英语,而"en_UK"表示英国英语。最后一部分,跟在一个句点之后,是可选的字符集说明符,例如"zh_CN.gb2312"表示中国使用gb2312字符集。
|
||||
|
||||
GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三部分,接下来我们都采用locale描述的前面两部分来作为i18n标准的locale名。
|
||||
@@ -7,10 +7,10 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
|
||||
>在Linux和Solaris系统中可以通过`locale -a`命令列举所有支持的地区名,读者可以看到这些地区名的命名规范。对于BSD等系统,没有locale命令,但是地区信息存储在/usr/share/locale中。
|
||||
|
||||
##设置Locale
|
||||
## 设置Locale
|
||||
有了上面对locale的定义,那么我们就需要根据用户的信息(访问信息、个人信息、访问域名等)来设置与之相关的locale,我们可以通过如下几种方式来设置用户的locale。
|
||||
|
||||
###通过域名设置Locale
|
||||
### 通过域名设置Locale
|
||||
设置Locale的办法这一就是在应用运行的时候采用域名分级的方式,例如,我们采用www.asta.com当做我们的英文站(默认站),而把域名www.asta.cn当做中文站。这样通过在应用里面设置域名和相应的locale的对应关系,就可以设置好地区。这样处理有几点好处:
|
||||
|
||||
- 通过URL就可以很明显的识别
|
||||
@@ -42,7 +42,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
|
||||
通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。
|
||||
|
||||
###从域名参数设置Locale
|
||||
### 从域名参数设置Locale
|
||||
目前最常用的设置Locale的方式是在URL里面带上参数,例如www.asta.com/hello?locale=zh或者www.asta.com/zh/hello。这样我们就可以设置地区:`i18n.SetLocale(params["locale"])`。
|
||||
|
||||
这种设置方式几乎拥有前面讲的通过域名设置Locale的所有优点,它采用RESTful的方式,以使得我们不需要增加额外的方法来处理。但是这种方式需要在每一个的link里面增加相应的参数locale,这也许有点复杂而且有时候甚至相当的繁琐。不过我们可以写一个通用的函数url,让所有的link地址都通过这个函数来生成,然后在这个函数里面增加`locale=params["locale"]`参数来缓解一下。
|
||||
@@ -51,7 +51,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
|
||||
mux.Get("/:locale/books", listbook)
|
||||
|
||||
###从客户端设置地区
|
||||
### 从客户端设置地区
|
||||
在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。
|
||||
|
||||
- Accept-Language
|
||||
@@ -75,7 +75,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
|
||||
当然你也可以让用户根据你提供的下拉菜单或者别的什么方式的设置相应的locale,然后我们将用户输入的信息,保存到与它帐号相关的profile中,当用户再次登陆的时候把这个设置复写到locale设置中,这样就可以保证该用户每次访问都是基于自己先前设置的locale来获得页面。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
通过上面的介绍可知,设置Locale可以有很多种方式,我们应该根据需求的不同来选择不同的设置Locale的方法,以让用户能以它最熟悉的方式,获得我们提供的服务,提高应用的用户友好性。
|
||||
|
||||
## links
|
||||
|
||||
10
10.2.md
10
10.2.md
@@ -1,6 +1,6 @@
|
||||
# 10.2 本地化资源
|
||||
前面小节我们介绍了如何设置Locale,设置好Locale之后我们需要解决的问题就是如何存储相应的Locale对应的信息呢?这里面的信息包括:文本信息、时间和日期、货币值、图片、包含文件以及视图等资源。那么接下来我们讲对这些信息一一进行介绍,Go语言中我们把这些格式信息存储在JSON中,然后通过合适的方式展现出来。(接下来以中文和英文两种语言对比举例,存储格式文件en.json和zh-CN.json)
|
||||
##本地化文本消息
|
||||
## 本地化文本消息
|
||||
文本信息是我们编写Web应用中最长用到的,也是本地化资源中最多的信息,你想要以适合本地的语言显示文本信息,那么就需要建立相应的map来维护一个key-value的关系,在打印消息之前从map中去获取相应的字符串,例如下面这个例如,一个包含英文和中文的食品名称的简单的map,以及一个从该map中抽取单词的函数。
|
||||
|
||||
package main
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
上面的示例代码主要是为了演示我们内部实现的思路,其实最后数据是存储在JSON里面的,map我们可以通过`json.Unmarshal`获取对应的数据。
|
||||
|
||||
##本地化日期和时间
|
||||
## 本地化日期和时间
|
||||
时间和日期对于不同的地区会显示不同的时间点,而且相应的显示时间格式也是不同的,例如中文环境下可能显示:`2012年10月24日 星期三 23时11分13秒 CST`,而在英文环境下可能显示:`Wed Oct 24 23:11:13 CST 2012`。这里面我们需要解决两点:
|
||||
|
||||
1. 时区问题
|
||||
@@ -77,7 +77,7 @@
|
||||
//%d 替换成24
|
||||
}
|
||||
|
||||
##本地化货币值
|
||||
## 本地化货币值
|
||||
各个地区的货币表示也不一样,我们想要根据不同的Locale显示不同的货币金额需要实现如上时间格式的处理函数,详细的实现请看下面代码:
|
||||
|
||||
en["money"] ="USD %d"
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
|
||||
|
||||
##本地化视图和资源
|
||||
## 本地化视图和资源
|
||||
我们在展现不同Locale的时候可能会根据不同的Locale采用不同的视图,这些视图里面包含不同的图片、css、js等各种静态资源。那么我们如何来处理这些信息呢?首先我们需要组织对应的locale文件信息,请看下面的文件目录安排:
|
||||
|
||||
views
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
这样我们在本地化视图以及资源的时候采用这种方式就可以很容易的进行扩展了。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
本小节介绍了如何使用存储本地资源,本地资源有些需要通过转换函数,有些通过lang设置,但是最后都是通过key-value的方式存储了相应的Locale对应数据,然后通过转换函数通过key读取出来相应的Locale信息,如果是文本信息就直接输出了,如果是时间日期或者货币需要结合`fmt.Printf`函数的处理才能转换成正确的信息展示,而对于不同Locale的视图和资源是最简单的,只要在路径里面增加lang就可以实现了。
|
||||
|
||||
## links
|
||||
|
||||
8
10.3.md
8
10.3.md
@@ -1,16 +1,16 @@
|
||||
# 10.3 国际化站点
|
||||
前面小节介绍了如何处理本地化资源,即Locale一个相应的配置文件,那么如果处理多个的本地化资源呢?而对于一些我们经常用到的例如:简单的文本翻译、时间日期、数字等如果处理呢?本小节将一一解决这些问题。
|
||||
##管理多个本地包
|
||||
## 管理多个本地包
|
||||
|
||||
##自动加载本地包
|
||||
## 自动加载本地包
|
||||
|
||||
##template mapfunc
|
||||
## template mapfunc
|
||||
|
||||
1. 文本信息
|
||||
2. 时间日期
|
||||
3. 数字
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [本地化资源](<10.2.md>)
|
||||
|
||||
8
2.1.md
8
2.1.md
@@ -1,8 +1,8 @@
|
||||
#2.1 你好,Go
|
||||
# 2.1 你好,Go
|
||||
|
||||
在开始编写应用之前,我们先从最基本的程序开始。就像你造房子之前不知道什么是地基一样,编写程序也不知道如何开始。因此,在本节中,我们要学习用最基本的语法让Go程序运行起来。
|
||||
|
||||
##程序
|
||||
## 程序
|
||||
|
||||
这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
Hello, world or καλημ ́ρα κóσμ or こんにちは世界
|
||||
|
||||
##详解
|
||||
## 详解
|
||||
首先我们要了解一个概念,Go程序是通过`package`来组织的
|
||||
|
||||
`package <pkgName>`(在我们的例子中是`package main`)这一行告诉我们当前文件属于哪个包,而包名`main`则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了`main`包之外,其它的包最后都会生成`*.a`文件(也就是包文件)并放置在`$GOPATH/pkg/$GOOS_$GOARCH`中(以Mac为例就是`$GOPATH/pkg/darwin_amd64`)。
|
||||
@@ -42,7 +42,7 @@
|
||||
最后大家可以看到我们输出的内容里面包含了很多非ASCII码字符。实际上,Go是天生支持UTF-8的,任何字符都可以直接输出,你甚至可以用UTF-8中的任何字符作为标识符。
|
||||
|
||||
|
||||
##结论
|
||||
## 结论
|
||||
|
||||
Go使用`package`(和Python的模块类似)来组织代码。`main.main()`函数(这个函数主要位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者),所以它天生就具有多语言的支持。
|
||||
|
||||
|
||||
36
2.2.md
36
2.2.md
@@ -1,8 +1,8 @@
|
||||
#2.2 Go基础
|
||||
# 2.2 Go基础
|
||||
|
||||
这小节我们将要介绍如何定义变量、常量、Go内置类型以及一些Go程序设计中的技巧。
|
||||
|
||||
##定义变量
|
||||
## 定义变量
|
||||
|
||||
Go语言里面定义变量有多种方式。
|
||||
|
||||
@@ -61,7 +61,7 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的
|
||||
var i int
|
||||
}
|
||||
|
||||
##常量
|
||||
## 常量
|
||||
|
||||
所谓常量,也就是在编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。
|
||||
|
||||
@@ -81,9 +81,9 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的
|
||||
const Pi float32 = 3.1415926
|
||||
|
||||
|
||||
##内置基础类型
|
||||
## 内置基础类型
|
||||
|
||||
###Boolean
|
||||
### Boolean
|
||||
|
||||
在Go中,布尔值的类型为`bool`,可用的值是`true`或`false`,默认为`false`。
|
||||
|
||||
@@ -97,7 +97,7 @@ 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`的别称。
|
||||
|
||||
@@ -120,7 +120,7 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的
|
||||
|
||||
它会打印:(5+5i)
|
||||
|
||||
###字符串
|
||||
### 字符串
|
||||
|
||||
我们在上一节中讲过,Go中的字符串都是用`UTF-8`的形式编码的。字符串通过用一对双引号(`""`)或反引号(`` ` ```` ` ``)括起来定义,它的类型是`string`。
|
||||
|
||||
@@ -168,7 +168,7 @@ Go中可以使用`+`来链接两个字符串:
|
||||
|
||||
`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。
|
||||
|
||||
###错误类型
|
||||
### 错误类型
|
||||
Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误:
|
||||
|
||||
err := errors.New("emit macho dwarf: elf header corrupted")
|
||||
@@ -176,15 +176,15 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`
|
||||
fmt.Print(err)
|
||||
}
|
||||
|
||||
###Go数据底层的存储
|
||||
### Go数据底层的存储
|
||||
|
||||
下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。
|
||||
|
||||

|
||||
|
||||
##一些技巧
|
||||
## 一些技巧
|
||||
|
||||
###分组声明
|
||||
### 分组声明
|
||||
|
||||
在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
|
||||
|
||||
@@ -222,7 +222,7 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`
|
||||
|
||||
>除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。
|
||||
|
||||
###iota枚举
|
||||
### iota枚举
|
||||
|
||||
Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,每调用一次加1:
|
||||
|
||||
@@ -235,14 +235,14 @@ Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采
|
||||
|
||||
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
|
||||
|
||||
###Go程序设计的一些规则
|
||||
### Go程序设计的一些规则
|
||||
Go之所以会那么简洁,是因为它有一些默认的行为:
|
||||
- 大写字母开头的变量是已导出的,也就是其它包可以读取的,类似`class`中`public`的概念;小写字母开头的就是未导出的
|
||||
- 大写字母开头的函数也是一样,相当于`public`的函数;小写字母开头的就是类似`private`
|
||||
|
||||
##array、slice、map
|
||||
## array、slice、map
|
||||
|
||||
###array
|
||||
### array
|
||||
`array`就是数组,它的定义方式如下:
|
||||
|
||||
var arr [n]type
|
||||
@@ -277,7 +277,7 @@ Go之所以会那么简洁,是因为它有一些默认的行为:
|
||||

|
||||
|
||||
|
||||
###slice
|
||||
### slice
|
||||
|
||||
在很多应用场景中,数组并不能满足我们的需求。在刚开始时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice`
|
||||
|
||||
@@ -361,7 +361,7 @@ slice有一些简便的操作
|
||||
注:`append`函数会改变`slice`所引用的数组的内容,从而影响到引用同一数组的其它`slice`。
|
||||
但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。
|
||||
|
||||
###map
|
||||
### map
|
||||
|
||||
`map`也就是Python中字典的概念,它的格式为`map[keyType]valueType`
|
||||
|
||||
@@ -412,7 +412,7 @@ slice有一些简便的操作
|
||||
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
|
||||
|
||||
|
||||
###make、new操作
|
||||
### make、new操作
|
||||
|
||||
`make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。
|
||||
|
||||
|
||||
28
2.3.md
28
2.3.md
@@ -1,8 +1,8 @@
|
||||
#2.3 流程和函数
|
||||
# 2.3 流程和函数
|
||||
这小节我们要介绍Go里面的流程控制以及函数操作
|
||||
##流程控制
|
||||
## 流程控制
|
||||
流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的描述来表达很复杂的事情。
|
||||
###if
|
||||
### if
|
||||
`if`也许是所有语言中最常见的了,它的语法概括起来就是:`如果满足条件就做某事,否则做另一件事`
|
||||
|
||||
Go里面`if`条件语法中不需要括号,如下代码所示
|
||||
@@ -35,7 +35,7 @@ Go的`if`还有一个强大的地方就是条件里面允许声明一个变量
|
||||
fmt.Println("The integer is greater than 3")
|
||||
}
|
||||
|
||||
###goto
|
||||
### goto
|
||||
|
||||
Go有`goto`语句——请明智地使用它。用`goto`跳转到一定是当前函数内定义的标签。例如假设这样一个循环:
|
||||
|
||||
@@ -49,7 +49,7 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到一定是当前
|
||||
|
||||
标签名是大小写敏感的。
|
||||
|
||||
###for
|
||||
### for
|
||||
Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下:
|
||||
|
||||
for expression1; expression2; expression3 {
|
||||
@@ -117,7 +117,7 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
|
||||
}
|
||||
|
||||
|
||||
###switch
|
||||
### switch
|
||||
有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下
|
||||
|
||||
switch sExpr {
|
||||
@@ -176,7 +176,7 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
|
||||
default case
|
||||
|
||||
|
||||
##函数
|
||||
## 函数
|
||||
函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下:
|
||||
|
||||
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
|
||||
@@ -223,7 +223,7 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
|
||||
|
||||
上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。
|
||||
|
||||
###多个返回值
|
||||
### 多个返回值
|
||||
Go语言和C相比,更先进的地方,其中一点就是能够返回多个值,也许这个思想来源于Python。
|
||||
|
||||
我们直接上代码看例子
|
||||
@@ -254,7 +254,7 @@ Go语言和C相比,更先进的地方,其中一点就是能够返回多个
|
||||
return
|
||||
}
|
||||
|
||||
###变参
|
||||
### 变参
|
||||
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
|
||||
|
||||
func myfunc(arg ...int) {}
|
||||
@@ -264,7 +264,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
|
||||
fmt.Printf("And the number is: %d\n", n)
|
||||
}
|
||||
|
||||
###传值与传指针
|
||||
### 传值与传指针
|
||||
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为我们作用在了copy上面。
|
||||
|
||||
为了验证我们上面的说法,我们来看一个例子
|
||||
@@ -323,7 +323,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
|
||||
- 传指针比较轻量级 (8 bytes)只是传内存地址,我们可以通过指针高效的传递大的结构体。如果传值的话,那么每次传递, 在copy上面就会花费大量的时间和内存。所以记住了,当你要传递大的结构体的时候,用指针是一个明智的选择。
|
||||
- Go语言中`string`,`slice`,`map`这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变`slice`的长度,则仍需要取地址传递指针)
|
||||
|
||||
###defer
|
||||
### defer
|
||||
Go里面有一个不错的设计,就是回调函数,有点类似面向对象语言里面的析构函数,当函数执行完之后再执行。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
|
||||
|
||||
func ReadWrite() bool {
|
||||
@@ -363,7 +363,7 @@ Go里面有一个不错的设计,就是回调函数,有点类似面向对象
|
||||
defer fmt.Printf("%d ", i)
|
||||
}
|
||||
|
||||
###函数作为值、类型
|
||||
### 函数作为值、类型
|
||||
|
||||
在Go中函数也是一种变量,我们可以通过`type`来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
|
||||
|
||||
@@ -413,7 +413,7 @@ Go里面有一个不错的设计,就是回调函数,有点类似面向对象
|
||||
|
||||
函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
|
||||
|
||||
###Panic和Recover
|
||||
### Panic和Recover
|
||||
|
||||
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了`panic`和`recover`机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有`panic`的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?
|
||||
|
||||
@@ -445,7 +445,7 @@ Recover
|
||||
return
|
||||
}
|
||||
|
||||
###`main`函数和`init`函数
|
||||
### `main`函数和`init`函数
|
||||
|
||||
Go里面有两个保留的函数:`init`函数(能够应用于所有的`package`)和`main`函数(只能应用于`package main`)。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个`package`中只写一个`init`函数。
|
||||
|
||||
|
||||
6
2.4.md
6
2.4.md
@@ -1,5 +1,5 @@
|
||||
#2.4 struct类型
|
||||
##struct
|
||||
# 2.4 struct类型
|
||||
## struct
|
||||
Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示:
|
||||
|
||||
type person struct {
|
||||
@@ -78,7 +78,7 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型
|
||||
bob.name, paul.name, bp_Older.name, bp_diff)
|
||||
}
|
||||
|
||||
###struct的匿名字段
|
||||
### struct的匿名字段
|
||||
我们上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
|
||||
|
||||
当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
|
||||
|
||||
10
2.5.md
10
2.5.md
@@ -1,7 +1,7 @@
|
||||
#2.5面向对象
|
||||
# 2.5面向对象
|
||||
前面两章我们介绍了函数和struct,那你是否想过函数当作struct的字段一样来处理呢?今天我们就讲解一下函数的另一种形态,带有接收者的函数,我们称为`method`
|
||||
|
||||
##method
|
||||
## method
|
||||
现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现
|
||||
|
||||
package main
|
||||
@@ -213,7 +213,7 @@ method的语法如下:
|
||||
|
||||
上面的代码通过文字描述出来之后一看是不是很简单?我们一般解决问题都是通过问题的描述,然后去用这样的代码实现。
|
||||
|
||||
###指针作为receivers
|
||||
### 指针作为receivers
|
||||
现在让我们回头看看上面的SetColor的method,它的receiver是一个指向Box的指针,是的,你可以使用*Box。想想为啥要使用指针而不是Box本身呢?
|
||||
|
||||
我们先来看看我们上面SetColor的真正目的,我们是想改变这个Box的颜色,那么如果我们不传Box的指针,那么我们接受的其实是Box的一个copy,如果改变了颜色值,其实是修改的copy,而不是真正的Box。所以我们需要传入指针。
|
||||
@@ -236,7 +236,7 @@ method的语法如下:
|
||||
|
||||
所以,你不用担心你是调用的指针的method还是不是指针的method,Go知道你要做的一切,这对于有多年C/C++编程经验的同学来说,真是解决了一个很大的痛苦。
|
||||
|
||||
###method继承
|
||||
### method继承
|
||||
前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子
|
||||
|
||||
package main
|
||||
@@ -271,7 +271,7 @@ method的语法如下:
|
||||
sam.SayHi()
|
||||
}
|
||||
|
||||
###method重载
|
||||
### method重载
|
||||
上面的例子中,如果Emplyee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Emplyee上面定义一个method,重载了匿名字段的方法。请看下面的例子
|
||||
|
||||
package main
|
||||
|
||||
20
2.6.md
20
2.6.md
@@ -1,8 +1,8 @@
|
||||
#2.6interface
|
||||
# 2.6interface
|
||||
|
||||
##interface
|
||||
## interface
|
||||
Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服。
|
||||
###什么是interface
|
||||
### 什么是interface
|
||||
简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。
|
||||
|
||||
我们前面一章最后一个例子中Student和Employee都能Sayhi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能`say hi`
|
||||
@@ -12,7 +12,7 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容
|
||||
这样Student实现了三个方法:Sayhi、Sing、BorrowMoney;而Employee实现了Sayhi、Sing、SpendSalary。
|
||||
|
||||
上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface:Sayhi和Sing,也就是这两个对象是该interface类型。而Employee没有实现这个interface:Sayhi、Sing和BorrowMoney,因为Employee没有实现BorrowMoney这个方法。
|
||||
###interface类型
|
||||
### interface类型
|
||||
interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子
|
||||
|
||||
type Human struct {
|
||||
@@ -87,7 +87,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
|
||||
|
||||
最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
|
||||
|
||||
###interface值
|
||||
### interface值
|
||||
那么interface里面到底能存什么值呢?如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。例如上面例子中,我们定义了一个Men interface类型的变量m,那么m里面可以存Human、Student或者Employee值。
|
||||
|
||||
因为m能够持有这三种类型的对象,所以我们可以定义一个包含Men类型元素的slice,这个slice可以被赋予实现了Men接口的任意结构的对象,这个和我们传统意义上面的slice有所不同。
|
||||
@@ -172,7 +172,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
|
||||
|
||||
通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, go 通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
|
||||
|
||||
###空interface
|
||||
### 空interface
|
||||
空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
|
||||
|
||||
// 定义a为空接口
|
||||
@@ -184,7 +184,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
|
||||
a = s
|
||||
|
||||
一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
|
||||
###interface函数参数
|
||||
### interface函数参数
|
||||
interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。
|
||||
|
||||
举个例子:我们已经知道fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
|
||||
@@ -222,7 +222,7 @@ interface的变量可以持有任意实现该interface类型的对象,这给
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
||||
|
||||
注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。
|
||||
###interface变量存储的类型
|
||||
### interface变量存储的类型
|
||||
|
||||
我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
|
||||
|
||||
@@ -322,7 +322,7 @@ interface的变量可以持有任意实现该interface类型的对象,这给
|
||||
|
||||
这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。
|
||||
|
||||
###嵌入interface
|
||||
### 嵌入interface
|
||||
Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
|
||||
|
||||
我们可以看到源码包container/heap里面有这样的一个定义
|
||||
@@ -353,7 +353,7 @@ Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Str
|
||||
Writer
|
||||
}
|
||||
|
||||
###反射
|
||||
### 反射
|
||||
Go语言实现了反射,所谓反射就是动态运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
|
||||
|
||||
下面我简要的讲解一下一般的使用,我们使用reflect大概的分成三步,首先我们要去反射是一个类型的值(这些值都实现了空interface),需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
|
||||
|
||||
12
2.7.md
12
2.7.md
@@ -1,8 +1,8 @@
|
||||
#2.7 并发
|
||||
# 2.7 并发
|
||||
|
||||
有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而GO从语言层面就支持了并行。
|
||||
|
||||
##Goroutines
|
||||
## Goroutines
|
||||
|
||||
Goroutines是Go并行设计的核心。Goroutines说到底其实就是线程,但是他比线程更小,十几个Goroutines可能体现在底层就是五六个线程,Go语言内部帮你实现了这些Goroutines之间的内存共享。Go语言的作者经常说着这样一句话,不要通过共享来通信,而要通过通信来共享。
|
||||
|
||||
@@ -43,7 +43,7 @@ Goroutines是通过Go的runtime管理的一个线程管理器。Goroutines通过
|
||||
hello
|
||||
|
||||
我们可以看到go关键字很方便的就实现了并发编程。
|
||||
##channels
|
||||
## channels
|
||||
Goroutines运行在相同的地址空间,因此访问共享内存必须做好同步。那么Goroutines之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel 时,也需要定义发送到channel 的值的类型。注意,必须使用make 创建channel:
|
||||
|
||||
ci := make(chan int)
|
||||
@@ -82,7 +82,7 @@ channel通过操作符`<-`来接收和发送数据
|
||||
|
||||
默认情况下,channel接收和发送数据都是阻塞的除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel 是在多个goroutine之间同步很棒的工具。
|
||||
|
||||
##Buffered Channels
|
||||
## Buffered Channels
|
||||
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
|
||||
|
||||
ch := make(chan type, value)
|
||||
@@ -105,7 +105,7 @@ channel通过操作符`<-`来接收和发送数据
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
|
||||
##Range和Close
|
||||
## Range和Close
|
||||
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
|
||||
|
||||
package main
|
||||
@@ -137,7 +137,7 @@ channel通过操作符`<-`来接收和发送数据
|
||||
|
||||
>另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
|
||||
|
||||
##Select
|
||||
## Select
|
||||
我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。
|
||||
|
||||
`select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
|
||||
|
||||
2
2.8.md
2
2.8.md
@@ -1,4 +1,4 @@
|
||||
#2.8总结
|
||||
# 2.8总结
|
||||
|
||||
这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。
|
||||
|
||||
|
||||
14
3.1.md
14
3.1.md
@@ -1,4 +1,4 @@
|
||||
#3.1 Web工作方式
|
||||
# 3.1 Web工作方式
|
||||
|
||||
我们平时浏览网页的时候,打开浏览器,输入网址,按下回车键,然后就出来了内容。在这个看似简单的行为背后,到底隐藏了些什么呢?
|
||||
|
||||
@@ -17,7 +17,7 @@ Web服务器的工作原理简单的可以归纳为:
|
||||
|
||||
一个简单的事务处理事件就是这样实现的,看起来很复杂,做起来其实是挺简单的,需要注意的是客户机与服务器之间的通信是非连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。
|
||||
|
||||
##URL和DNS解析
|
||||
## URL和DNS解析
|
||||
我们浏览网页都是通过URL访问的,那么URL到底是怎么样的呢?
|
||||
|
||||
URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下
|
||||
@@ -56,7 +56,7 @@ URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用
|
||||
|
||||
通过上面的步骤,我们最后获取的是IP地址,也就是浏览器最后发起请求的时候是基于IP来和服务器做信息交互的。
|
||||
|
||||
##HTTP协议详解
|
||||
## HTTP协议详解
|
||||
|
||||
HTTP协议是Web工作的核心,所以要了解清楚Web的工作方式就需要详细的了解清楚HTTP是怎么样工作的。
|
||||
|
||||
@@ -66,7 +66,7 @@ HTTP协议是无状态的,同一个客户端的这次请求和上次请求是
|
||||
|
||||
>HTTP协议是建立在TCP协议之上的,因此TCP攻击一样会影响HTTP的通讯,例如比较常见的一些攻击:SYN Flood是当前最流行的DoS(拒绝服务攻击)与DdoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。
|
||||
|
||||
###HTTP请求信息(浏览器信息)
|
||||
### HTTP请求信息(浏览器信息)
|
||||
|
||||
我们先来看看Request消息的结构, Request 消息分为3部分,第一部分叫Request line, 第二部分叫Request header,第三部分是body。header和body之间有个空行,详细的如下所示
|
||||
|
||||
@@ -95,7 +95,7 @@ Http协议定义了很多与服务器交互的方法,最基本的有4种,分
|
||||
2. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
|
||||
3. GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。
|
||||
|
||||
###HTTP响应信息(服务器信息)
|
||||
### HTTP响应信息(服务器信息)
|
||||
我们再来看看HTTP的response信息,他的结构如下:
|
||||
|
||||
- 状态行:HTTP版本 服务器状态(比如:404找不到...) 描述信息
|
||||
@@ -123,7 +123,7 @@ HTTP/1.1中定义了5类状态码, 状态码由三位数字组成,第一个
|
||||

|
||||
|
||||
|
||||
###HTTP协议是无状态的和Connection: keep-alive的区别
|
||||
### HTTP协议是无状态的和Connection: keep-alive的区别
|
||||
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。
|
||||
|
||||
HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
|
||||
@@ -132,7 +132,7 @@ HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保
|
||||
|
||||
Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间
|
||||
|
||||
##请求实例
|
||||
## 请求实例
|
||||
|
||||

|
||||
|
||||
|
||||
4
3.2.md
4
3.2.md
@@ -1,8 +1,8 @@
|
||||
#3.2 GO搭建一个web服务器
|
||||
# 3.2 GO搭建一个web服务器
|
||||
|
||||
前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的web服务。同时使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置和操作。
|
||||
|
||||
##http包建立web服务器
|
||||
## http包建立web服务器
|
||||
|
||||
package main
|
||||
|
||||
|
||||
6
3.3.md
6
3.3.md
@@ -1,7 +1,7 @@
|
||||
#3.3 Go如何使得Web工作
|
||||
# 3.3 Go如何使得Web工作
|
||||
前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?万变不离其宗,Go也离不开我们第一小节介绍的Web工作方式。
|
||||
|
||||
##对应web工作方式的几个概念
|
||||
## 对应web工作方式的几个概念
|
||||
|
||||
以下均是服务器端的相应概念
|
||||
|
||||
@@ -13,7 +13,7 @@ Conn:用户的每次请求链接
|
||||
|
||||
Handler:处理请求和生成返回信息的处理逻辑
|
||||
|
||||
##分析http包运行机制
|
||||
## 分析http包运行机制
|
||||
|
||||
如下图所示,是Go实现Web工作模式的流程图
|
||||
|
||||
|
||||
8
3.4.md
8
3.4.md
@@ -1,9 +1,9 @@
|
||||
#3.4 Go的http包详解
|
||||
# 3.4 Go的http包详解
|
||||
前面小节介绍了Go怎么样实现了Web工作模式的一个流程,这一小节,我们将来详细的解剖一下http包,看它到底怎么样实现整个的过程的。
|
||||
|
||||
Go的http有两个核心功能:Conn、ServeMux
|
||||
|
||||
##Conn的goroutine
|
||||
## Conn的goroutine
|
||||
与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
|
||||
|
||||
Go在等待客户端请求里面是这样写的:
|
||||
@@ -16,7 +16,7 @@ Go在等待客户端请求里面是这样写的:
|
||||
|
||||
这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到handler的时候可以读取到相应的header信息,这样保证了每个请求的独立性。
|
||||
|
||||
##ServeMux的自定义
|
||||
## ServeMux的自定义
|
||||
我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?
|
||||
|
||||
它的结构如下:
|
||||
@@ -103,7 +103,7 @@ handler是一个接口,但是前一小节中的`sayhelloName`函数并没有
|
||||
http.ListenAndServe(":9090", mux)
|
||||
}
|
||||
|
||||
##Go代码的执行流程
|
||||
## Go代码的执行流程
|
||||
|
||||
通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。
|
||||
|
||||
|
||||
2
3.5.md
2
3.5.md
@@ -1,4 +1,4 @@
|
||||
#3.5小结
|
||||
# 3.5小结
|
||||
这一章我们介绍了HTTP协议, DNS解析的过程, 如何用go实现一个简陋的web server。并深入到net/http包的源码中为大家揭开如何实现此server的秘密。
|
||||
|
||||
我希望通过这一章的学习,你能够对Go开发Web有了初步的了解,我们也看到相应的代码了,Go开发Web应用是很方便的,同时又是相当的灵活。
|
||||
|
||||
2
4.1.md
2
4.1.md
@@ -1,4 +1,4 @@
|
||||
#4.1处理表单的输入
|
||||
# 4.1处理表单的输入
|
||||
|
||||
我们先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.gtpl(放入当前新建项目的目录里面)
|
||||
|
||||
|
||||
24
4.2.md
24
4.2.md
@@ -1,10 +1,10 @@
|
||||
#4.2验证表单的输入
|
||||
# 4.2验证表单的输入
|
||||
|
||||
我们开发Web的一个原则就是,不能信任用户输入的任何信息,所以验证和过滤用户的输入信息就变得相当重要,我们经常会在微博、新闻中听到某某网站被入侵了,存在什么漏洞,这些大多是是因为对于用户输入的信息没有做严格的验证引起的,所以为了编写出安全可靠的Web程序,验证表单输入变得相当的重要。
|
||||
|
||||
我们平常编写Web应用主要有两方面的数据验证,一个是在页面端的js验证(目前很多这方面的验证库),一个是在服务器端的验证,我们这小节讲解的是如何在服务器端验证。
|
||||
|
||||
##必填字段
|
||||
## 必填字段
|
||||
你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数`len`可以获取字符串的长度,这样我们就可以通过len来测试获取数据的长度,例如:
|
||||
|
||||
if len(r.Form["username"][0])==0{
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
`r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值,如果是map的值,必须通过上面的方式来获取。
|
||||
|
||||
##数字
|
||||
## 数字
|
||||
你想要确保一个表单输入框中获取的只能是数字,例如,你想通过表单获取某个人的具体年龄是50岁还是10岁,而不是像“一把年纪了”或“年轻着呢”这种描述
|
||||
|
||||
如果我们是判断正整数,那么我们先转化成int类型,然后进行处理
|
||||
@@ -38,14 +38,14 @@
|
||||
|
||||
>Go实现的正则是[RE2](http://code.google.com/p/re2/wiki/Syntax),所有的字符都是UTF-8编码的。
|
||||
|
||||
##中文
|
||||
## 中文
|
||||
有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有效的验证只有正则方式来验证,如下代码所示
|
||||
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
##英文
|
||||
## 英文
|
||||
我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。
|
||||
|
||||
我们可以很简单的通过正则验证数据:
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
|
||||
|
||||
##电子邮件地址
|
||||
## 电子邮件地址
|
||||
你想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证:
|
||||
|
||||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||||
@@ -65,14 +65,14 @@
|
||||
}
|
||||
|
||||
|
||||
##手机号码
|
||||
## 手机号码
|
||||
你想要判断用户输入的手机号码是否正确,通过正则也可以验证:
|
||||
|
||||
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
##下拉菜单
|
||||
## 下拉菜单
|
||||
如果我们想要判断表单里面`<select>`元素生成的下拉菜单中是否有被选中的项目。有些时候黑客可能会伪造这个下拉菜单不存在的值发送给你,那么如何判断这个值是否是我们预设的值呢?
|
||||
|
||||
我们的select可能是这样的一些元素
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
上面这个函数包含在我开源的一个库里面(操作slice和map的库),[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
|
||||
|
||||
##单选按钮
|
||||
## 单选按钮
|
||||
如果我们想要判断radio按钮是否有一个被选中了,我们页面的输出可能就是一个男、女性别的选择,但是也可能一个15岁大的无聊小孩,一手拿着http协议的书,另一只手通过telnet客户端向你的程序在发送请求呢,你设定的性别男值是1,女是2,他给你发送一个3,你的程序会出现异常吗?因此我们也需要像下拉菜单的判断方式类似,判断我们获取的值是我们预设的值,而不是额外的值。
|
||||
|
||||
<input type="radio" name="gender" value="1">男
|
||||
@@ -113,7 +113,7 @@
|
||||
}
|
||||
return false
|
||||
|
||||
##复选框
|
||||
## 复选框
|
||||
有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。
|
||||
|
||||
<input type="checkbox" name="interest" value="football">足球
|
||||
@@ -131,7 +131,7 @@
|
||||
return false
|
||||
|
||||
|
||||
##日期和时间
|
||||
## 日期和时间
|
||||
你想确定用户填写的日期或时间是否有效。例如
|
||||
,你想确保用户在日程表中安排8月份的第45天开会,或者提供还没到的时间作为生日。
|
||||
|
||||
@@ -142,7 +142,7 @@ Go里面提供了一个time的处理包,我们可以把用户的输入年月
|
||||
|
||||
获取时间之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。
|
||||
|
||||
##身份证号码
|
||||
## 身份证号码
|
||||
如果我们想验证表单输入的是否是身份证,通过正则也可以方便的验证,但是身份证有15位和18位,我们两个都需要验证
|
||||
|
||||
//验证15位身份证,15位的是全部数字
|
||||
|
||||
2
4.3.md
2
4.3.md
@@ -1,4 +1,4 @@
|
||||
#4.3 预防跨站脚本
|
||||
# 4.3 预防跨站脚本
|
||||
|
||||
现在的网站包含大量的动态内容以提高用户体验,比过去要复杂得多。所谓动态内容,就是根据用户环境和需要,Web应用程序能够输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响。
|
||||
|
||||
|
||||
2
4.4.md
2
4.4.md
@@ -1,4 +1,4 @@
|
||||
#4.4防止多次递交表单
|
||||
# 4.4防止多次递交表单
|
||||
|
||||
不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?
|
||||
|
||||
|
||||
4
4.5.md
4
4.5.md
@@ -1,4 +1,4 @@
|
||||
#4.5处理文件上传
|
||||
# 4.5处理文件上传
|
||||
你想处理一个由用户上传的文件。比如你正在建设一个类似Instagram的网站,你需要存储用户拍摄的照片。这种需求该如何实现呢?
|
||||
|
||||
文件要能够上传,首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
|
||||
@@ -79,7 +79,7 @@
|
||||

|
||||
|
||||
|
||||
##客户端上传文件
|
||||
## 客户端上传文件
|
||||
|
||||
我们上面的例子演示了如何通过表单上传文件,然后在服务器端处理文件,其实Go支持模拟客户端表单功能支持文件上传,详细用法请看如下示例:
|
||||
|
||||
|
||||
2
4.6.md
2
4.6.md
@@ -1,4 +1,4 @@
|
||||
#4.6 小结
|
||||
# 4.6 小结
|
||||
这一章里面我们学习了Go里面如何处理表单信息,我们通过一个登陆、一个上传例子展示了Go处理form表单信息,处理文件上传的能力。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全、数据过滤就变得相当重要了,因此专门一个小节讲解了各方面的数据过滤,顺带讲了一下Go里面对正则的处理。
|
||||
|
||||
通过这一章能够让你了解客户端和服务器端如何进行数据的交互,让客户端的数据进入我们的系服务器统,让我们系统处理之后的数据展现给客户端。
|
||||
|
||||
28
5.1.md
28
5.1.md
@@ -1,7 +1,7 @@
|
||||
#5.1 database/sql接口
|
||||
# 5.1 database/sql接口
|
||||
Go和PHP不同的地方是,他没有官方提供数据库驱动,而是为开发数据库驱动定义了一些标准接口,第三方用户可以根据定义的接口来开发相应的数据库驱动,这样做有一个好处,我们按照标准接口开发的代码, 在需要迁移数据库时,不需要任何修改。那么Go都定义了那些标准接口呢?让我们来详细的分析一下
|
||||
|
||||
##sql.Register
|
||||
## sql.Register
|
||||
这个存在于database/sql的函数是用来注册数据库驱动的,当第三方开发者开发数据库驱动时,都会实现init函数,在init里面会调用这个`Register(name string, driver driver.Driver)`完成本驱动的注册。
|
||||
|
||||
我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的:
|
||||
@@ -34,7 +34,7 @@ Go和PHP不同的地方是,他没有官方提供数据库驱动,而是为开
|
||||
>
|
||||
>新手都会被这个`_`所迷惑,其实这个就是Go设计的巧妙之处,我们在变量赋值的时候经常看到这个,它是用来忽略变量的占位符,那么这个包引入也是,这儿的意思是引入此包而不直接使用这个包中定义的函数,变量等资源,我们在2.3流程和函数里面介绍过init函数的初始化过程,包在引入的时候会去调用包的init函数以完成对包的初始化,因此我们引入上面的数据库驱动包之后会去调用init函数,然后在init函数里面注册了这个数据库驱动,这样我们就可以在接下来的代码中直接使用这个数据库驱动了。
|
||||
|
||||
##driver.Driver
|
||||
## driver.Driver
|
||||
Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。
|
||||
|
||||
type Driver interface {
|
||||
@@ -52,7 +52,7 @@ Driver是一个数据库驱动的接口,他定义了一个method: Open(name
|
||||
|
||||
第三方驱动都会定义这个函数,它会解析name参数来获取相关数据库的连接信息,解析完成后,它将使用此信息来初始化一个Conn并返回它。
|
||||
|
||||
##driver.Conn
|
||||
## driver.Conn
|
||||
Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面,详情请参考上面的说明。
|
||||
|
||||
type Conn interface {
|
||||
@@ -67,7 +67,7 @@ Close函数关闭当前的连接,以及执行释放连接拥有的资源等清
|
||||
|
||||
Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询,更新等操作,或者对事务进行回滚、递交。
|
||||
|
||||
##driver.Stmt
|
||||
## driver.Stmt
|
||||
Stmt是一种准备好的状态,和Conn相关联,而且是只能应用于一个goroutine中,不能应用在多个goroutine中。
|
||||
|
||||
type Stmt interface {
|
||||
@@ -86,7 +86,7 @@ Exec函数执行Prepare准备好的sql,传入参数执行update/insert等操
|
||||
Query函数执行Prepare准备好的sql,传入需要的参数执行select操作,返回Rows结果集
|
||||
|
||||
|
||||
##driver.Tx
|
||||
## driver.Tx
|
||||
事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以
|
||||
|
||||
type Tx interface {
|
||||
@@ -96,7 +96,7 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操
|
||||
|
||||
这两个函数一个用来递交一个事务,一个用来回滚事务。
|
||||
|
||||
##driver.Execer
|
||||
## driver.Execer
|
||||
这是一个Conn可选择实现的接口
|
||||
|
||||
type Execer interface {
|
||||
@@ -105,7 +105,7 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操
|
||||
|
||||
如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。
|
||||
|
||||
##driver.Result
|
||||
## driver.Result
|
||||
这个是执行Update/Insert等操作返回的结果接口定义
|
||||
|
||||
type Result interface {
|
||||
@@ -117,7 +117,7 @@ LastInsertId函数返回由数据库执行插入操作得到的自动增长ID号
|
||||
|
||||
RowsAffected函数返回query操作影响的数据条目数。
|
||||
|
||||
##driver.Rows
|
||||
## driver.Rows
|
||||
Rows是执行查询返回的结果集接口定义
|
||||
|
||||
type Rows interface {
|
||||
@@ -136,7 +136,7 @@ Close函数用来关闭Rows迭代器。
|
||||
Next函数用来返回下一条数据,把数据赋值给dest。dest里面的元素必须是driver.Value的值除了string,返回的数据里面所有的string都必须要转换成[]byte。如果最后没数据了,Next函数最后返回io.EOF。
|
||||
|
||||
|
||||
##driver.RowsAffected
|
||||
## driver.RowsAffected
|
||||
RowsAffested其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式
|
||||
|
||||
type RowsAffected int64
|
||||
@@ -145,7 +145,7 @@ RowsAffested其实就是一个int64的别名,但是他实现了Result接口,
|
||||
|
||||
func (v RowsAffected) RowsAffected() (int64, error)
|
||||
|
||||
##driver.Value
|
||||
## driver.Value
|
||||
Value其实就是一个空接口,他可以容纳任何的数据
|
||||
|
||||
type Value interface{}
|
||||
@@ -159,7 +159,7 @@ Value的值必须所有的驱动里面控制的,Value要么是nil,要么是
|
||||
string [*]除了Rows.Next返回的不能是string.
|
||||
time.Time
|
||||
|
||||
##driver.ValueConverter
|
||||
## driver.ValueConverter
|
||||
ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口
|
||||
|
||||
type ValueConverter interface {
|
||||
@@ -172,7 +172,7 @@ ValueConverter接口定义了如何把一个普通的值转化成driver.Value的
|
||||
- 把数据库查询结果转化成driver.Value值
|
||||
- 在scan函数里面如何把dirve.Value值转化成用户定义的值
|
||||
|
||||
##driver.Valuer
|
||||
## driver.Valuer
|
||||
Valuer接口定义了返回一个driver.Value的方式
|
||||
|
||||
type Valuer interface {
|
||||
@@ -182,7 +182,7 @@ Valuer接口定义了返回一个driver.Value的方式
|
||||
|
||||
通过上面的讲解,你应该对于驱动的开发有了一个基本的了解,一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了,在此不再赘述。
|
||||
|
||||
##database/sql
|
||||
## database/sql
|
||||
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还实现了一个建议的conn pool。
|
||||
|
||||
type DB struct {
|
||||
|
||||
6
5.2.md
6
5.2.md
@@ -1,7 +1,7 @@
|
||||
#5.2使用MySQL数据库
|
||||
# 5.2使用MySQL数据库
|
||||
目前Internet上流行的网站构架方式是LAMP,其中的M即MySQL, 作为数据库,MySQL以免费、开源、使用方便为优势成为了很多Web开发的后端数据库存储引擎。
|
||||
|
||||
##MySQL驱动
|
||||
## MySQL驱动
|
||||
Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持database/sql标准,而有些是采用了自己的实现接口,常用的有如下几种:
|
||||
|
||||
- http://code.google.com/p/go-mysql-driver/ 支持database/sql,全部采用go写。
|
||||
@@ -14,7 +14,7 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data
|
||||
- 完全支持database/sql接口
|
||||
- 支持keepalive,保持长连接,虽然[星星](http://www.mikespook.com)fork的mymysql也支持keepalive,但不是线程安全的,这个从底层就支持了keepalive。
|
||||
|
||||
##示例代码
|
||||
## 示例代码
|
||||
接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。
|
||||
|
||||
CREATE TABLE `userinfo` (
|
||||
|
||||
6
5.3.md
6
5.3.md
@@ -1,8 +1,8 @@
|
||||
#5.3使用SQLite数据库
|
||||
# 5.3使用SQLite数据库
|
||||
|
||||
SQLite 是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。 与其他数据库管理系统不同,SQLite 的安装和运行非常简单,在大多数情况下,只要确保SQLite的二进制文件存在即可开始创建、连接和使用数据库。如果您正在寻找一个嵌入式数据库项目或解决方案,SQLite是绝对值得考虑。SQLite可以是说开源的Access。
|
||||
|
||||
##驱动
|
||||
## 驱动
|
||||
Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接口的
|
||||
|
||||
- https://github.com/mattn/go-sqlite3 支持database/sql接口,基于cgo(关于cgo的知识请参看官方文档或者本书后面的章节)写的
|
||||
@@ -11,7 +11,7 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接
|
||||
|
||||
目前支持database/sql的SQLite数据库驱动只有第一个,我目前也是采用它来开发项目的。采用标准接口有利于以后出现更好的驱动的时候做迁移。
|
||||
|
||||
##实例代码
|
||||
## 实例代码
|
||||
示例的数据库表结构如下所示,相应的建表SQL:
|
||||
|
||||
CREATE TABLE `userinfo` (
|
||||
|
||||
6
5.4.md
6
5.4.md
@@ -1,4 +1,4 @@
|
||||
#5.4使用PostgreSQL数据库
|
||||
# 5.4使用PostgreSQL数据库
|
||||
|
||||
PostgreSQL 是一个自由的对象-关系数据库服务器(数据库管理系统),它在灵活的 BSD-风格许可证下发行。它提供了相对其他开放源代码数据库系统(比如 MySQL 和 Firebird),和对专有系统比如 Oracle、Sybase、IBM 的 DB2 和 Microsoft SQL Server的一种选择。
|
||||
|
||||
@@ -6,7 +6,7 @@ PostgreSQL和MySQL比较,它更加庞大一点,因为它是用来替代Oracl
|
||||
|
||||
现在MySQL被Oracle收购之后,有传闻Oracle正在逐步的封闭MySQL,,鉴于此,将来我们也许会选择PostgreSQL而不是MySQL作为项目的后端数据库。
|
||||
|
||||
##驱动
|
||||
## 驱动
|
||||
Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发中使用了这个数据库。
|
||||
|
||||
- https://github.com/bmizerany/pq 支持database/sql驱动,纯Go写的
|
||||
@@ -15,7 +15,7 @@ Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发
|
||||
|
||||
在下面的示例中我采用了第一个驱动,因为它目前使用的人最多,在github上也比较活跃。
|
||||
|
||||
##实例代码
|
||||
## 实例代码
|
||||
数据库建表语句:
|
||||
|
||||
CREATE TABLE userinfo
|
||||
|
||||
20
5.5.md
20
5.5.md
@@ -1,4 +1,4 @@
|
||||
#5.5使用beedb库进行ORM开发
|
||||
# 5.5使用beedb库进行ORM开发
|
||||
beedb是我开发的一个Go进行ORM操作的库,它采用了Go style方式对数据库进行操作,实现了struct到数据表记录的映射。beedb是一个十分轻量级的Go ORM框架,开发这个库的本意降低复杂的ORM学习曲线,尽可能在ORM的运行效率和功能之间寻求一个平衡,beedb是目前开源的Go ORM框架中实现比较完整的一个库,而且运行效率相当不错,功能也基本能满足需求。但是目前还不支持关系关联,这个是接下来版本升级的重点。
|
||||
|
||||
beedb是支持database/sql标准接口的ORM库,所以理论上来说,只要数据库驱动支持database/sql接口就可以无缝的接入beedb。目前我测试过的驱动包括下面几个:
|
||||
@@ -15,13 +15,13 @@ MS ADODB: github.com/mattn/go-adodb[*]
|
||||
|
||||
ODBC: bitbucket.org/miquella/mgodbc[*]
|
||||
|
||||
##安装
|
||||
## 安装
|
||||
|
||||
beedb支持go get方式安装,是完全按照Go Style的方式来实现的。
|
||||
|
||||
go get github.com/astaxie/beedb
|
||||
|
||||
##如何初始化
|
||||
## 如何初始化
|
||||
首先你需要import相应的数据库驱动包、database/sql标准接口包以及beedb包,如下所示:
|
||||
|
||||
import (
|
||||
@@ -64,7 +64,7 @@ beedb的New函数实际上应该有两个参数,第一个参数标准接口的
|
||||
|
||||
>注意一点,beedb针对驼峰命名会自动帮你转化成下划线字段,例如你定义了Struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。
|
||||
|
||||
##插入数据
|
||||
## 插入数据
|
||||
下面的代码演示了如何插入一条记录,可以看到我们操作的是strcut对象,而不是原生的sql语句,最后通过调用Save接口将数据保存到数据库。
|
||||
|
||||
var saveone Userinfo
|
||||
@@ -101,7 +101,7 @@ beedb接口提供了另外一种插入的方式,map数据插入。
|
||||
|
||||
上面我们调用的SetTable函数是显式的告诉ORM,我要执行的这个map对应的数据库表是`userinfo`。
|
||||
|
||||
##更新数据
|
||||
## 更新数据
|
||||
继续上面的例子来演示更新操作,现在saveone的主键已经有值了,此时调用save接口,beedb内部会自动调用update以进行数据的更新而非插入操作。
|
||||
|
||||
saveone.Username = "Update Username"
|
||||
@@ -122,7 +122,7 @@ SetPK:显式的告诉ORM,数据库表`userinfo`的主键是`uid`。
|
||||
Where:用来设置条件,支持多个参数,第一个参数如果为整数,相当于调用了Where("主键=?",值)。
|
||||
Updata函数接收map类型的数据,执行更新数据。
|
||||
|
||||
##查询数据
|
||||
## 查询数据
|
||||
beedb的查询接口比较灵活,具体使用请看下面的例子
|
||||
|
||||
例子1,根据主键获取数据:
|
||||
@@ -180,7 +180,7 @@ OrderBy:这个函数用来进行查询排序,参数是需要排序的条件。
|
||||
|
||||
FindMap()函数返回的是`[]map[string][]byte`类型,所以你需要自己作类型转换。
|
||||
|
||||
##删除数据
|
||||
## 删除数据
|
||||
beedb提供了丰富的删除数据接口,请看下面的例子
|
||||
|
||||
例子1,删除单条数据
|
||||
@@ -198,7 +198,7 @@ beedb提供了丰富的删除数据接口,请看下面的例子
|
||||
orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow()
|
||||
|
||||
|
||||
##关联查询
|
||||
## 关联查询
|
||||
目前beedb还不支持struct的关联关系,但是有些应用却需要用到连接查询,所以现在beedb提供了一个简陋的实现方案:
|
||||
|
||||
a, _ := orm.SetTable("userinfo").Join("LEFT", "userdeatail", "userinfo.uid=userdeatail.uid").Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdeatail.profile").FindMap()
|
||||
@@ -210,7 +210,7 @@ beedb提供了丰富的删除数据接口,请看下面的例子
|
||||
- 第三个参数表示连接的条件
|
||||
|
||||
|
||||
##Group By和Having
|
||||
## Group By和Having
|
||||
针对有些应用需要用到group by和having的功能,beedb也提供了一个简陋的实现
|
||||
|
||||
a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap()
|
||||
@@ -221,7 +221,7 @@ GroupBy:用来指定进行groupby的字段
|
||||
|
||||
Having:用来指定having执行的时候的条件
|
||||
|
||||
##进一步的发展
|
||||
## 进一步的发展
|
||||
目前beedb已经获得了很多来自国内外用户的反馈,我目前也正在考虑重构,接下来会在几个方面进行改进
|
||||
|
||||
- 实现interface设计,类似databse/sql/driver的设计,设计beedb的接口,然后去实现相应数据库的CRUD操作
|
||||
|
||||
6
5.6.md
6
5.6.md
@@ -1,9 +1,9 @@
|
||||
#5.6NOSQL数据库操作
|
||||
# 5.6NOSQL数据库操作
|
||||
NoSQL(Not Only SQL),指的是非关系型的数据库。随着Web2.0的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
|
||||
|
||||
而Go语言作为21世纪的C语言,对NOSQL的支持也是很好,目前流行的NOSQL主要有redis、mongoDB、Cassandra和Membase等。这些数据库都有高性能、高并发读写等特点,目前已经广泛应用于各种应用中。我接下来主要讲解一下redis和mongoDB的操作。
|
||||
|
||||
##redis
|
||||
## redis
|
||||
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。
|
||||
|
||||
目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的[互联网企业](http://redis.io/topics/whos-using-redis)
|
||||
@@ -52,7 +52,7 @@ https://github.com/astaxie/goredis
|
||||
|
||||
我们可以看到操作redis非常的方便,而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。
|
||||
|
||||
##mongoDB
|
||||
## mongoDB
|
||||
|
||||
MongoDB是一个高性能,开源,无模式的文档型数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,采用的是类似json的bjson格式来存储数据,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
|
||||
|
||||
|
||||
2
5.7.md
2
5.7.md
@@ -1,4 +1,4 @@
|
||||
#5.7小结
|
||||
# 5.7小结
|
||||
这一章我们讲解了Go如何设计database/sql接口,然后介绍了各种第三方关系型数据库驱动的使用。接着介绍了beedb,一种基于关系型数据库的ORM库,如何对数据库进行简单的操作。最后介绍了NOSQL的一些知识,目前Go对于NOSQL支持还是不错,因为Go作为21世纪的C语言,那么对于21世纪的数据库也是支持的相当好。
|
||||
|
||||
通过这一章的学习,我们学会了如何操作各种数据库,那么就解决了我们数据存储的问题,这是Web里面最重要的一部分,所以希望大家能够深入的去了解database/sql的设计思想。
|
||||
|
||||
2
5.md
2
5.md
@@ -1,4 +1,4 @@
|
||||
#5 访问数据库
|
||||
# 5 访问数据库
|
||||
对许多Web应用程序而言,数据库都是其核心所在。数据库几乎可以用来存储你想查询和修改的任何信息,比如用户信息、产品目录或者新闻列表等。
|
||||
|
||||
Go没有内置的驱动支持任何的数据库,但是Go定义了database/sql接口,用户可以基于驱动接口开发相应数据库的驱动,5.1小节里面介绍Go设计的一些驱动,介绍Go是如何设计数据库驱动接口的。5.2至5.4小节介绍目前使用的比较多的一些关系型数据驱动已经如何使用,5.5小节介绍我自己开发一个ORM库,基于database/sql标准接口开发的,可以兼容几乎所有支持database/sql的数据库驱动,可以方便的使用Go style来进行数据库操作。
|
||||
|
||||
12
6.1.md
12
6.1.md
@@ -1,4 +1,4 @@
|
||||
#6.1 session和cookie
|
||||
# 6.1 session和cookie
|
||||
session和cookie是网站浏览中较为常见的两个概念,也是比较难以辨析的两个概念,但它们在浏览需要认证的服务页面以及页面统计中却相当关键。我们先来了解一下session和cookie怎么来的?考虑这样一个问题:
|
||||
|
||||
如何抓取一个访问受限的网页?如新浪微博好友的主页,个人微博页面等。
|
||||
@@ -15,7 +15,7 @@ session,简而言之就是在服务器上保存用户操作的历史信息。
|
||||
|
||||

|
||||
|
||||
##cookie
|
||||
## cookie
|
||||
Cookie是由浏览器维持的,存储在客户端的一小段文本信息,伴随着用户请求和页面在Web服务器和浏览器之间传递。用户每次访问站点时,Web应用程序都可以读取cookie包含的信息。浏览器设置里面有cookie隐私数据选项,打开它,可以看到很多已访问网站的cookies,如下图所示:
|
||||
|
||||

|
||||
@@ -27,7 +27,7 @@ cookie是有时间限制的,根据生命期不同分成两种:会话cookie
|
||||
如果设置了过期时间(setMaxAge(60*60*24)),浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
|
||||
|
||||
|
||||
###Go设置cookie
|
||||
### Go设置cookie
|
||||
Go语言中通过net/http包中的SetCookie来设置:
|
||||
|
||||
http.SetCookie(w ResponseWriter, cookie *Cookie)
|
||||
@@ -60,7 +60,7 @@ w表示需要写入的response,cookie是一个struct,让我们来看一下co
|
||||
http.SetCookie(w, &cookie)
|
||||
|
||||
|
||||
###Go读取cookie
|
||||
### Go读取cookie
|
||||
上面的例子演示了如何设置cookie数据,我们这里来演示一下如何读取cookie
|
||||
|
||||
cookie, _ := r.Cookie("username")
|
||||
@@ -74,7 +74,7 @@ w表示需要写入的response,cookie是一个struct,让我们来看一下co
|
||||
|
||||
可以看到通过request获取cookie非常方便。
|
||||
|
||||
##session
|
||||
## session
|
||||
|
||||
session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义。
|
||||
|
||||
@@ -86,7 +86,7 @@ session机制是一种服务器端的机制,服务器使用一种类似于散
|
||||
|
||||
session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的。
|
||||
|
||||
##小结
|
||||
## 小结
|
||||
|
||||
如上文所述,session和cookie的目的相同,都是为了克服http协议无状态的缺陷,但完成的方法不同。session通过cookie,在客户端保存session id,而将用户的其他会话消息保存在服务端的session对象中,与此相对的,cookie需要将所有信息都保存在客户端。因此cookie存在着一定的安全隐患,例如本地cookie中保存的用户名密码被破译,或cookie被其他网站收集(例如:1. appA主动设置域B cookie,让域B cookie获取;2. XSS,在appA上通过javascript获取document.cookie,并传递给自己的appB)。
|
||||
|
||||
|
||||
22
6.2.md
22
6.2.md
@@ -1,7 +1,7 @@
|
||||
#6.2 Go如何使用session
|
||||
# 6.2 Go如何使用session
|
||||
通过上一小节的介绍,我们知道session是在服务器端实现的一种用户和服务器之间认证的解决方案,目前Go标准包没有为session提供任何支持,这小节我们将会自己动手来实现go版本的session管理和创建。
|
||||
|
||||
##session创建过程
|
||||
## session创建过程
|
||||
session的基本原理是由服务器为每个会话维护一份信息数据,客户端和服务端依靠一个全局唯一的标识来访问这份数据,以达到交互的目的。当用户访问Web应用时,服务端程序会随需要创建session,这个过程可以概括为三个步骤:
|
||||
|
||||
- 生成全局唯一标识符(sessionid);
|
||||
@@ -15,10 +15,10 @@ session的基本原理是由服务器为每个会话维护一份信息数据,
|
||||
2. URL重写
|
||||
所谓URL重写,就是在返回给用户的页面里的所有的URL后面追加session标识符,这样用户在收到响应之后,无论点击响应页面里的哪个链接或提交表单,都会自动带上session标识符,从而就实现了会话的保持。虽然这种做法比较麻烦,但是,如果客户端禁用了cookie的话,此种方案将会是首选。
|
||||
|
||||
##Go实现session管理
|
||||
## Go实现session管理
|
||||
通过上面session创建过程的讲解,读者应该对session有了一个大体的认识,但是具体到动态页面技术里面,又是怎么实现session的呢?下面我们将结合session的生命周期(lifecycle),来实现go语言版本的session管理。
|
||||
|
||||
###session管理设计
|
||||
### session管理设计
|
||||
我们知道session管理涉及到如下几个因素
|
||||
|
||||
- 全局session管理器
|
||||
@@ -29,7 +29,7 @@ session的基本原理是由服务器为每个会话维护一份信息数据,
|
||||
|
||||
接下来我将讲解一下我关于session管理的整个设计思路以及相应的go代码示例:
|
||||
|
||||
###Session管理器
|
||||
### Session管理器
|
||||
|
||||
定义一个全局的session管理器
|
||||
|
||||
@@ -96,7 +96,7 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全部
|
||||
provides[name] = provide
|
||||
}
|
||||
|
||||
###全局唯一的Session ID
|
||||
### 全局唯一的Session ID
|
||||
|
||||
Session ID是用来识别访问Web应用的每一个用户,因此必须保证它是全局唯一的(GUID),下面代码展示了如何满足这一需求:
|
||||
|
||||
@@ -108,7 +108,7 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
###session创建
|
||||
### session创建
|
||||
我们需要为每个来访用户分配或获取与他相关连的Session,以便后面根据Session信息来验证操作。SessionStart这个函数就是用来检测是否已经有某个Session与当前来访用户发生了关联,如果没有则创建之。
|
||||
|
||||
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
|
||||
@@ -142,7 +142,7 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证
|
||||
}
|
||||
}
|
||||
|
||||
###操作值:设置、读取和删除
|
||||
### 操作值:设置、读取和删除
|
||||
SessionStart函数返回的是一个满足Session接口的变量,那么我们该如何用他来对session数据进行操作呢?
|
||||
|
||||
上面的例子中的代码`session.Get("uid")`已经展示了基本的读取数据的操作,现在我们再来看一下详细的操作:
|
||||
@@ -171,7 +171,7 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们
|
||||
|
||||
因为Session有过期的概念,所以我们定义了GC操作,当访问过期时间满足GC的触发条件后将会引起GC,但是当我们进行了任意一个session操作,都会对Session实体进行更新,都会触发对最后访问时间的修改,这样当GC的时候就不会误删除还在使用的Session实体。
|
||||
|
||||
###session重置
|
||||
### session重置
|
||||
我们知道,Web应用中有用户退出这个操作,那么当用户退出应用的时候,我们需要对该用户的session数据进行销毁操作,上面的代码已经演示了如何使用session重置操作,下面这个函数就是实现了这个功能:
|
||||
|
||||
//Destroy sessionid
|
||||
@@ -190,7 +190,7 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们
|
||||
}
|
||||
|
||||
|
||||
###session销毁
|
||||
### session销毁
|
||||
我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动:
|
||||
|
||||
func init() {
|
||||
@@ -206,7 +206,7 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们
|
||||
|
||||
我们可以看到GC充分利用了time包中的定时器功能,当超时`maxLifeTime`之后调用GC函数,这样就可以保证`maxLifeTime`时间内的session都是可用的,类似的方案也可以用于统计在线用户数之类的。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
至此 我们实现了一个用来在Web应用中全局管理Session的SessionManager,定义了用来提供Session存储实现Provider的接口,下一小节,我们将会通过接口定义来实现一些Provider,供大家参考学习。
|
||||
|
||||
## links
|
||||
|
||||
2
6.3.md
2
6.3.md
@@ -1,4 +1,4 @@
|
||||
#6.3 session存储
|
||||
# 6.3 session存储
|
||||
上一节我们介绍了Session管理器的实现原理,定义了存储session的接口,这小节我们将示例一个基于内存的session存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码
|
||||
|
||||
package memory
|
||||
|
||||
10
6.4.md
10
6.4.md
@@ -1,8 +1,8 @@
|
||||
#6.4 预防session劫持
|
||||
# 6.4 预防session劫持
|
||||
session劫持是一种广泛存在的比较严重的安全威胁,在session技术中,客户端和服务端通过session的标识符来维护会话, 但这个标识符很容易就能被嗅探到,从而被其他人利用.它是中间人攻击的一种类型。
|
||||
|
||||
本节将通过一个实例来演示会话劫持,希望通过这个实例,能让读者更好地理解session的本质。
|
||||
##session劫持过程
|
||||
## session劫持过程
|
||||
我们写了如下的代码来展示一个count计数器:
|
||||
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -41,8 +41,8 @@ count.gtpl的代码如下所示:
|
||||

|
||||
|
||||
可以看到虽然换了浏览器,但是我们却获得了sessionID,然后模拟了cookie存储的过程。这个例子是在同一台计算机上做的,不过即使换用两台来做,其结果仍然一样。此时如果交替点击两个浏览器里的链接你会发现它们其实操纵的是同一个计数器。不必惊讶,此处firefox盗用了chrome和goserver之间的维持会话的钥匙,即gosessionid,这是一种类型的“会话劫持”。在goserver看来,它从http请求中得到了一个gosessionid,由于HTTP协议的无状态性,它无法得知这个gosessionid是从chrome那里“劫持”来的,它依然会去查找对应的session,并执行相关计算。与此同时 chrome也无法得知自己保持的会话已经被“劫持”。
|
||||
##session劫持防范
|
||||
###cookieonly和token
|
||||
## session劫持防范
|
||||
### cookieonly和token
|
||||
通过上面session劫持的简单演示可以了解到session一旦被其他人劫持,就非常危险,劫持者可以假装成被劫持者进行很多非法操作。那么如何有效的防止session劫持呢?
|
||||
|
||||
其中一个解决方案就是sessionID的值只允许cookie设置,而不是通过URL重置方式设置,同时设置cookie的httponly为true,这个属性是设置是否可通过客户端脚本访问这个设置的cookie,第一这个可以防止这个cookie被XSS读取从而引起session劫持,第二cookie设置不会像URL重置方式那么容易获取sessionID。
|
||||
@@ -59,7 +59,7 @@ count.gtpl的代码如下所示:
|
||||
sess.Set("token",token)
|
||||
|
||||
|
||||
###间隔生成新的SID
|
||||
### 间隔生成新的SID
|
||||
还有一个解决方案就是,我们给session额外设置一个创建时间的值,一旦过了一定的时间,我们销毁这个sessionID,重新生成新的session,这样可以一定程度上防止session劫持的问题。
|
||||
|
||||
createtime := sess.Get("createtime")
|
||||
|
||||
2
6.5.md
2
6.5.md
@@ -1,4 +1,4 @@
|
||||
#6.5 小结
|
||||
# 6.5 小结
|
||||
这章我们学习了什么是session,什么是cookie,以及他们两者之间的关系。但是目前Go官方标准包里面不支持session,所以我们设计了一个session管理器,实现了session从创建到销毁的整个过程。然后定义了Provider的接口,使得可以支持各种后端的session存储,然后我们在第三小节里面介绍了如何使用内存存储来实现session的管理。第四小节我们讲解了session劫持的过程,以及我们如何有效的来防止session劫持。通过这一章的讲解,希望能够让读者了解整个sesison的执行原理以及如何实现,而且是如何更加安全的使用session。
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
2
6.md
2
6.md
@@ -1,4 +1,4 @@
|
||||
#6 session和数据存储
|
||||
# 6 session和数据存储
|
||||
Web开发中一个很重要的议题就是如何做好用户的整个浏览过程的控制,因为HTTP协议是无状态的,所以用户的每一次请求都是无状态的,我们不知道在整个Web操作过程中哪些连接与该用户有关,我们应该如何来解决这个问题呢?Web里面经典的解决方案是cookie和session,cookie机制是一种客户端机制,把用户数据保存在客户端,而session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息,每一个网站访客都会被分配给一个唯一的标志符,即sessionID,它的存放形式无非两种:要么经过url传递,要么保存在客户端的cookies里.当然,你也可以将Session保存到数据库里,这样会更安全,但效率方面会有所下降。
|
||||
|
||||
6.1小节里面讲介绍session机制和cookie机制的关系和区别,6.2讲解Go语言如何来实现session,里面讲实现一个简易的session管理器,6.3小节讲解如何防止session被劫持的情况,如何有效的保护session。我们知道session其实可以存储在任何地方,6.3小节里面实现的session是存储在内存中的,但是如果我们的应用进一步扩展了,要实现应用的session共享,那么我们可以把session存储在数据库中(memcache或者redis),6.4小节将详细的讲解如何实现这些功能。
|
||||
|
||||
6
7.1.md
6
7.1.md
@@ -1,4 +1,4 @@
|
||||
#7.1 XML处理
|
||||
# 7.1 XML处理
|
||||
XML作为一种数据交换和信息传递的格式已经十分普及。而随着Web服务日益广泛的应用,现在XML在日常的开发工作中也扮演了愈发重要的角色。这一小节, 我们将就Go语言标准包中的XML相关处理的包进行介绍。
|
||||
|
||||
这个小节不会涉及XML规范相关的内容(如需了解相关知识请参考其他文献),而是介绍如何用Go语言来编解码XML文件相关的知识。
|
||||
@@ -19,7 +19,7 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随
|
||||
|
||||
上面的XML文档描述了两个服务器的信息,包含了服务器名和服务器的IP信息,接下来的Go例子以此XML描述的信息进行操作。
|
||||
|
||||
##解析XML
|
||||
## 解析XML
|
||||
如何解析如上这个XML文件喃呢? 我们可以通过xml包的`Unmarshal`函数来达到我们的目的
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
@@ -120,7 +120,7 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的
|
||||
|
||||
>注意: 为了正确解析,go语言的xml包要求struct定义中的所有字段必须是可导出的(即首字母大写)
|
||||
|
||||
##输出XML
|
||||
## 输出XML
|
||||
假若我们不是要解析如上所示的XML文件,而是生成它,那么在go语言中又该如何实现呢? xml包中提供了`Marshal`和`MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示:
|
||||
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
|
||||
10
7.2.md
10
7.2.md
@@ -1,4 +1,4 @@
|
||||
#7.2 JSON处理
|
||||
# 7.2 JSON处理
|
||||
JSON(Javascript Object Notation)是一种轻量级的数据交换语言,以文字为基础,具有自我描述性且易于让人阅读。尽管JSON是Javascript的一个子集,但JSON是独立于语言的文本格式,并且采用了类似于C语言家族的一些习惯。JSON与XML最大的不同在于XML是一个完整的标记语言,而JSON不是。JSON由于比XML更小、更快,更易解析,以及浏览器的内建快速解析支持,使得其更适用于网络数据传输领域。目前我们看到很多的开放平台,基本上都是采用了JSON作为他们的数据交互的接口。既然JSON在Web开发中如此重要,那么Go语言对JSON支持的怎么样呢?Go语言的标准库已经非常好的支持了JSON,可以很容易的对JSON数据进行编、解码的工作。
|
||||
|
||||
前一小节的运维的例子用json来表示,结果描述如下:
|
||||
@@ -6,9 +6,9 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
|
||||
|
||||
本小节余下的内容将以此JSON数据为基础,来介绍go语言的json包对JSON数据的编、解码。
|
||||
##解析JSON
|
||||
## 解析JSON
|
||||
|
||||
###解析到结构体
|
||||
### 解析到结构体
|
||||
假如有了上面的JSON串,那么我们如何来解析这个JSON串呢?Go的JSON包中有如下函数
|
||||
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
@@ -46,7 +46,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
|
||||
聪明的你一定注意到了这一点:能够被赋值的字段必须是可导出字段(即首字母大写)。同时JSON解析的时候只会解析能找得到的字段,如果找不到的字段会被忽略,这样的一个好处是:当你接收到一个很大的JSON数据结构而你却只想获取其中的部分数据的时候,你只需将你想要的数据对应的字段名大写,即可轻松解决这个问题。
|
||||
|
||||
###解析到interface
|
||||
### 解析到interface
|
||||
上面那种解析方式是在我们知晓被解析的JSON数据的结构的前提下采取的方案,如果我们不知道被解析的数据的格式,又应该如何来解析呢?
|
||||
|
||||
我们知道interface{}可以用来存储任意数据类型的对象,这种数据结构正好用于存储解析的未知结构的json数据的结果。JSON包中采用map[string]interface{}和[]interface{}结构来存储任意的JSON对象和数组。Go类型和JSON类型的对应关系如下:
|
||||
@@ -118,7 +118,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,
|
||||
|
||||
可以看到,使用这个库操作JSON比起官方包来说,简单的多,详细的请参考如下地址:https://github.com/bitly/go-simplejson
|
||||
|
||||
##生成JSON
|
||||
## 生成JSON
|
||||
我们开发很多应用的时候,最后都是要输出JSON数据串,那么如何来处理呢?JSON包里面通过`Marshal`函数来处理,函数定义如下:
|
||||
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
|
||||
6
7.3.md
6
7.3.md
@@ -1,4 +1,4 @@
|
||||
#7.3 正则处理
|
||||
# 7.3 正则处理
|
||||
正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何想你要得到的字符组合。如果你在Web开发中需要从一些文本数据源中获取数据,那么你只需要按照它的语法规则,随需构造出正确的模式字符串就能够从原数据源提取出有意义的文本信息。
|
||||
|
||||
Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果你已经使用过其他编程语言提供的正则相关功能,那么你应该对Go语言版本的不会太陌生,但是它们之间也有一些小的差异,因为Go实现的是RE2标准,除了\C,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax。
|
||||
@@ -7,7 +7,7 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
|
||||
|
||||
如果你还记得,在前面表单验证的小节里,我们已经接触过正则处理,在那里我们利用了它来验证输入的信息是否满足某些预设的条件。在使用中需要注意的一点就是:所有的字符都是UTF-8编码的。接下来让我们更加深入的来学习Go语言的`regexp`包相关知识吧。
|
||||
|
||||
##通过正则判断是否匹配
|
||||
## 通过正则判断是否匹配
|
||||
`regexp`包中含有三个函数用来判断是否匹配,如果匹配返回true,否则返回false
|
||||
|
||||
func Match(pattern string, b []byte) (matched bool, error error)
|
||||
@@ -40,7 +40,7 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
|
||||
|
||||
在上面的两个小例子中,我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。
|
||||
|
||||
##通过正则获取内容
|
||||
## 通过正则获取内容
|
||||
Match模式只能用来对字符串的判断,而无法截取字符串的一部分、过滤字符串、或者提取出符合条件的一批字符串。如果想要满足这些需求,那就需要使用正则表达式的复杂模式。
|
||||
|
||||
我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据:
|
||||
|
||||
26
7.4.md
26
7.4.md
@@ -1,5 +1,5 @@
|
||||
#7.4 模板处理
|
||||
##什么是模板
|
||||
# 7.4 模板处理
|
||||
## 什么是模板
|
||||
你一定听说过一种叫做MVC的设计模式,Model处理数据,View展现结果,Controller控制用户的请求,至于View层的处理,在很多动态语言里面都是通过在静态HTML中插入动态语言生成的数据,例如JSP中通过插入`<%=....=%>`,PHP中通过插入`<?php.....?>`来实现的。
|
||||
|
||||
通过下面这个图可以说明模板的机制
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Web应用反馈给客户端的信息中的大部分内容是静态的,不变的,而另外少部分是根据用户的请求来动态生成的,例如要显示用户的访问记录列表。用户之间只有记录数据是不同的,而列表的样式则是固定的,此时采用模板可以复用很多静态代码。
|
||||
|
||||
##Go模板使用
|
||||
## Go模板使用
|
||||
在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子:
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -26,10 +26,10 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变
|
||||
- 不使用handler来写演示代码,而是每个测试一个main,方便测试
|
||||
- 使用`os.Stdout`代替`http.ResponseWriter`,因为`os.Stdout`实现了`io.Writer`接口
|
||||
|
||||
##模板中如何插入数据?
|
||||
## 模板中如何插入数据?
|
||||
上面我们演示了如何解析并渲染模板,接下来让我们来更加详细的了解如何把数据渲染出来。一个模板都是应用在一个Go的对象之上,Go对象的字段如何插入到模板中呢?
|
||||
|
||||
###字段操作
|
||||
### 字段操作
|
||||
Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子:
|
||||
|
||||
package main
|
||||
@@ -63,7 +63,7 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
|
||||
|
||||
如果模板中输出`{{.}}`,这个一般应用与字符串对象,默认会调用fmt包输出字符串的内容。
|
||||
|
||||
###输出嵌套字段内容
|
||||
### 输出嵌套字段内容
|
||||
上面我们例子展示了如何针对一个对象的字段输出,那么如果字段里面还有对象,如何来循环的输出这些内容呢?我们可以使用`{{with …}}…{{end}}`和`{{range …}}{{end}}`来进行数据的输出。详细的使用请看下面的例子:
|
||||
|
||||
package main
|
||||
@@ -103,14 +103,14 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
|
||||
###pipelines
|
||||
### pipelines
|
||||
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"name"的数据,他表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们如何来进行转化呢?
|
||||
|
||||
{{. | html}}
|
||||
|
||||
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。
|
||||
|
||||
###条件处理
|
||||
### 条件处理
|
||||
在Go模板里面如果需要进行条件判断,那么我们可以使用和Go语言的`if-else`语法类似的方式来咱先,如果pipeline为空,那么if就认为是false,下面的例子展示了如何使用`if-else`语法:
|
||||
|
||||
package main
|
||||
@@ -136,7 +136,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的
|
||||
|
||||
通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
|
||||
|
||||
###模板变量
|
||||
### 模板变量
|
||||
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前,Go语言通过申明的局部变量格式如下所示:
|
||||
|
||||
$variable := pipeline
|
||||
@@ -146,7 +146,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
###模板函数
|
||||
### 模板函数
|
||||
模板在输出对象的字段值时,采用了`fmt`包把对象转化成了字符串。但是有时候我们的需求可能不是这样的,例如有时候我们为了防止垃圾邮件发送者通过采集网页的方式来发送给我们的邮箱信息,我们希望把`@`替换成`at`例如:`astaxie at beego.me`,如果要实现这样的功能,我们就需要自定义函数来做这个功能。
|
||||
|
||||
每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联
|
||||
@@ -240,7 +240,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的
|
||||
}
|
||||
|
||||
|
||||
##Must操作
|
||||
## Must操作
|
||||
模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,注释是否正确的关闭,变量是否正确的书写。接下来我们演示一个例子,用Must来判断模板是否正确:
|
||||
|
||||
package main
|
||||
@@ -270,7 +270,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的
|
||||
The next one ought to fail.
|
||||
panic: template: check parse error with Must:1: unexpected "}" in command
|
||||
|
||||
##嵌套模板
|
||||
## 嵌套模板
|
||||
我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明
|
||||
|
||||
{{define "子模板名称"}}内容{{end}}
|
||||
@@ -332,7 +332,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "name"`类似这样的
|
||||
|
||||
>同一个集合类的模板是互相知晓的,如果同一模板被多个集合使用,则它需要在多个集合中分别解析
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
通过上面对模板的详细介绍,我们了解了如何把动态数据与模板融合:如何输出循环数据、如何自定义函数、如何嵌套模板等等。通过模板技术的应用,我们可以完成MVC模式中V的处理,接下来的章节我们将介绍如何来处理M和C。
|
||||
|
||||
## links
|
||||
|
||||
2
7.5.md
2
7.5.md
@@ -1,4 +1,4 @@
|
||||
#7.5 小结
|
||||
# 7.5 小结
|
||||
这一章给大家介绍了一些文本处理的工具,包括XML、JSON、正则和模板技术,XML和JSON是数据交互的工具,通过XML和JSON你可以表达各种含义,通过正则你可以处理文本(搜索、替换、截取),通过模板技术你可以展现这些数据给用户。这些都是你开发Web应用过程中需要用到的技术,通过这个小节的介绍你能够了解如何处理文本、展现文本。
|
||||
|
||||
## links
|
||||
|
||||
2
7.md
2
7.md
@@ -1,4 +1,4 @@
|
||||
#7 文本处理
|
||||
# 7 文本处理
|
||||
Web开发中对于文本处理是非常重要的一部分,我们往往需要对输出或者输入的内容进行处理,这里的文本包括字符串、数字、Json、XMl等等。Go语言作为一门高性能的语言,对这些文本的处理都有官方的标准库来支持。而且在你使用中你会发现Go标准库的一些设计相当的巧妙,而且对于使用者来说也很方便就能处理这些文本。本章我们将通过四个小节的介绍,让用户对Go语言处理文本有一个很好的认识。
|
||||
|
||||
XML是目前很多标准接口的交互语言,很多时候和一些Java编写的webserver进行交互都是基于XML标准进行交互,7.1小节将介绍如何处理XML文本,我们使用XML之后发现它太复杂了,现在很多互联网企业对外的API大多数采用了JSON格式,这种格式描述简单,但是又能很好的表达意思,7.2小节我们将讲述如何来处理这样的JSON格式数据。正则是一个让人又爱又恨的工具,它处理文本的能力非常强大,我们在前面表单验证里面已经有所领略它的强大,7.3小节将详细的更深入的讲解如何利用好Go的正则。Web开发中一个很重要的部分就是MVC分离,在Go语言的Web开发中V有一个专门的包来支持`template`,7.4小节将详细的讲解如何使用模版来进行输出内容。
|
||||
|
||||
26
8.1.md
26
8.1.md
@@ -1,33 +1,33 @@
|
||||
#8.1 Socket编程
|
||||
# 8.1 Socket编程
|
||||
在很多底层网络应用开发者的眼里一切编程都是Socket,话虽然有点夸张,但却也几乎如此了,现在的网络编程几乎都是用Socket来编程。你想过这些情景么?我们每天打开浏览器浏览网页时,浏览器进程怎么和Web服务器进行通信的呢?当你用QQ聊天时,QQ进程怎么和服务器或者是你的好友所在的QQ进程进行通信的呢?当你打开PPstream观看视频时,PPstream进程如何与视频服务器进行通信的呢? 如此种种,都是靠Socket来进行通信的,以一斑窥全豹,可见Socket编程在现代编程中占据了多么重要的地位,这一节我们将介绍Go语言中如何进行Socket编程。
|
||||
|
||||
##什么是Socket?
|
||||
## 什么是Socket?
|
||||
Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
|
||||
|
||||
常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
|
||||
##Socket如何通信
|
||||
## Socket如何通信
|
||||
网络中的进程之间如何通过Socket通信呢?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中需要互相通信的进程,就可以利用这个标志在他们之间进行交互。请看下面这个TCP/IP协议结构图
|
||||
|
||||

|
||||
|
||||
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是为什么说“一切皆Socket”。
|
||||
|
||||
##Socket基础知识
|
||||
## Socket基础知识
|
||||
通过上面的介绍我们知道Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,需要IP地址和端口。
|
||||
|
||||
###IPv4地址
|
||||
### IPv4地址
|
||||
目前的全球因特网所采用的协议族是TCP/IP协议。IP是TCP/IP协议中网络层的协议,是TCP/IP协议族的核心协议。目前主要采用的IP协议的版本号是4(简称为IPv4),发展至今已经使用了30多年。
|
||||
|
||||
IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上。近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址的发放愈趋紧张,前一段时间,据报道IPV4的地址已经发放完毕,我们公司目前很多服务器的IP都是一个宝贵的资源。
|
||||
|
||||
地址格式类似这样:127.0.0.1 172.122.121.111
|
||||
|
||||
###IPv6地址
|
||||
### IPv6地址
|
||||
IPv6是下一版本的互联网协议,也可以说是下一代互联网的协议,它是为了解决IPv4在实施过程中遇到的各种问题而被提出的,IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
|
||||
|
||||
地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7
|
||||
|
||||
###Go支持的IP类型
|
||||
### Go支持的IP类型
|
||||
在Go的`net`包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下:
|
||||
|
||||
type IP []byte
|
||||
@@ -57,7 +57,7 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协
|
||||
|
||||
执行之后你就会发现只要你输入一个IP地址就会给出相应的IP格式
|
||||
|
||||
##TCP Socket
|
||||
## TCP Socket
|
||||
当我们知道如何通过网络端口访问一个服务时,那么我们能够做什么呢?作为客户端来说,我们可以通过向远端某台机器的的某个网络端口发送一个请求,然后得到在机器的此端口上监听的服务反馈的信息。作为服务端,我们需要把服务绑定到某个指定端口,并且在此端口上监听,当有客户端来访问时能够读取信息并且写入反馈信息。
|
||||
|
||||
在Go语言的`net`包中有一个类型`TCPConn`,这个类型可以用来作为客户端和服务器端交互的通道,他有两个主要的函数:
|
||||
@@ -81,7 +81,7 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协
|
||||
- addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22".
|
||||
|
||||
|
||||
###TCP client
|
||||
### TCP client
|
||||
Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回一个`TCPConn`类型的对象,当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的`TCPConn`对象来进行数据交换。一般而言,客户端通过`TCPConn`对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下:
|
||||
|
||||
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
|
||||
@@ -141,7 +141,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
|
||||
通过上面的代码我们可以看出:首先程序将用户的输入作为参数`service`传入`net.ResolveTCPAddr`获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接`conn`,通过`conn`来发送请求信息,最后通过`ioutil.ReadAll`从`conn`中读取全部的文本,也就是服务端响应反馈的信息。
|
||||
|
||||
###TCP server
|
||||
### TCP server
|
||||
上面我们编写了一个TCP的客户端程序,也可以通过net包来创建一个服务器端程序,在服务器端我们需要绑定服务到指定的非激活端口,并监听此端口,当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数,函数定义如下:
|
||||
|
||||
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
|
||||
@@ -224,7 +224,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
|
||||
通过把业务处理分离到函数`handleClient`,我们就可以进一步地实现多并发执行了。看上去是不是很帅,增加`go`关键词就实现了服务端的多并发,从这个小例子也可以看出goroutine的强大之处。
|
||||
|
||||
###控制TCP连接
|
||||
### 控制TCP连接
|
||||
TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数:
|
||||
|
||||
func (c *TCPConn) SetTimeout(nsec int64) os.Error
|
||||
@@ -235,7 +235,7 @@ TCP有很多连接控制函数,我们平常用到比较多的有如下几个
|
||||
第二个函数用来设置客户端是否和服务器端一直保持着连接,即使没有任何的数据发送。
|
||||
|
||||
更多的内容请查看`net`包的文档。
|
||||
##UDP Socket
|
||||
## UDP Socket
|
||||
Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样,只有TCP换成了UDP而已。UDP的几个主要函数如下所示:
|
||||
|
||||
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
|
||||
@@ -316,7 +316,7 @@ Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端
|
||||
}
|
||||
}
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
通过对TCP和UDP Socket编程的描述和实现,可见Go已经完备地支持了Socket编程,而且使用起来相当的方便,Go提供了很多函数,通过这些函数可以很容易就编写出高性能的Socket应用。
|
||||
|
||||
|
||||
|
||||
6
8.2.md
6
8.2.md
@@ -1,4 +1,4 @@
|
||||
#8.2 WebSocket
|
||||
# 8.2 WebSocket
|
||||
WebSocket是HTML5的重要特性,它实现了基于浏览器的远程socket,它使浏览器和服务器可以进行全双工通信,许多浏览器(Firefox、Google Chrome和Safari)都已对此做了支持。
|
||||
|
||||
在WebSocket出现之前,为了实现即时通信,采用的技术都是“轮询”,即在特定的时间间隔内,由浏览器对服务器发出HTTP Request,服务器在收到请求后,返回最新的数据给浏览器刷新,“轮询”使得浏览器需要对服务器不断发出请求,这样会占用大量带宽。
|
||||
@@ -13,7 +13,7 @@ WebSocket URL的起始输入是ws://或是wss://(在SSL上)。下图展示
|
||||
|
||||

|
||||
|
||||
##WebSocket原理
|
||||
## WebSocket原理
|
||||
WebSocket的协议颇为简单,在第一次handshake通过以后,连接便建立成功,其后的通讯数据都是以”\x00″开头,以”\xFF”结尾。在客户端,这个是透明的,WebSocket组件会自动将原始数据“掐头去尾”。
|
||||
|
||||
浏览器发出WebSocket连接请求,然后服务器发出回应,然后连接建立成功,这个过程通常称为“握手” (handshaking)。请看下面的请求和反馈信息:
|
||||
@@ -34,7 +34,7 @@ WebSocket的协议颇为简单,在第一次handshake通过以后,连接便
|
||||
|
||||
将之作为响应头`Sec-WebSocket-Accept`的值反馈给客户端。
|
||||
|
||||
##Go实现WebSocket
|
||||
## Go实现WebSocket
|
||||
Go语言标准包里面没有提供对WebSocket的支持,但是在由官方维护的go.net子包中有对这个的支持,你可以通过如下的命令获取该包:
|
||||
|
||||
go get code.google.com/p/go.net/websocket
|
||||
|
||||
8
8.3.md
8
8.3.md
@@ -1,6 +1,6 @@
|
||||
#8.3 REST
|
||||
# 8.3 REST
|
||||
RESTful,是目前最为流行的一种互联网软件架构。因为它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。本小节我们将来学习它到底是一种什么样的架构?以及在Go里面如何来实现它。
|
||||
##什么是REST
|
||||
## 什么是REST
|
||||
REST(REpresentational State Transfer)这个概念,首次出现是在 2000年Roy Thomas Fielding(他是HTTP规范的主要编写者之一)的博士论文中,它指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful的。
|
||||
|
||||
要理解什么是REST,我们需要理解下面几个概念:
|
||||
@@ -43,7 +43,7 @@ Web应用要满足REST最重要的原则是:客户端和服务器之间的交互
|
||||
|
||||

|
||||
|
||||
##RESTful的实现
|
||||
## RESTful的实现
|
||||
Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现的,所以我们可以利用`net/http`包来自己实现,当然需要针对REST做一些改造,REST是根据不同的method来处理相应的资源,目前已经存在的很多自称是REST的应用,其实并没有真正的实现REST,我暂且把这些应用根据实现的method分成几个级别,请看下图:
|
||||
|
||||

|
||||
@@ -100,7 +100,7 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现
|
||||
|
||||
上面的代码演示了如何编写一个REST的应用,我们访问的资源是用户,我们通过不同的method来访问不同的函数,这里使用了第三方库`github.com/drone/routes`,在前面章节我们介绍过如何实现自定义的路由器,这个库实现了自定义路由和方便的路由规则映射,通过它,我们可以很方便的实现REST的架构。通过上面的代码可知,REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
REST是一种架构风格,汲取了WWW的成功经验:无状态,以资源为中心,充分利用HTTP协议和URI协议,提供统一的接口定义,使得它作为一种设计Web服务的方法而变得流行。在某种意义上,通过强调URI和HTTP等早期Internet标准,REST是对大型应用程序服务器时代之前的Web方式的回归。目前Go对于REST的支持还是很简单的,通过实现自定义的路由规则,我们就可以为不同的method实现不同的handle,这样就实现了REST的架构。
|
||||
|
||||
## links
|
||||
|
||||
14
8.4.md
14
8.4.md
@@ -1,11 +1,11 @@
|
||||
#8.4 RPC
|
||||
# 8.4 RPC
|
||||
前面几个小节我们介绍了如何基于Socket和HTTP来编写网络应用,通过学习我们了解了Socket和HTTP采用的是类似"信息交换"模式,即客户端发送一条信息到服务端,然后(一般来说)服务器端都会返回一定的信息以表示响应。客户端和服务端之间约定了交互信息的格式,以便双方都能够解析交互所产生的信息。但是很多独立的应用并没有采用这种模式,而是采用类似常规的函数调用的方式来完成想要的功能。
|
||||
|
||||
RPC就是想实现函数调用模式的网络化。客户端就像调用本地函数一样,然后客户端把这些参数打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后执行的结果反馈给客户端。
|
||||
|
||||
RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序之间携带信息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
|
||||
|
||||
##RPC工作原理
|
||||
## RPC工作原理
|
||||
|
||||

|
||||
|
||||
@@ -22,7 +22,7 @@ RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一
|
||||
- 9.客户句柄由内核接收消息
|
||||
- 10.客户接收句柄返回的数据
|
||||
|
||||
##Go RPC
|
||||
## Go RPC
|
||||
Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。
|
||||
|
||||
Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:
|
||||
@@ -40,7 +40,7 @@ T、T1和T2类型必须能被`encoding/gob`包编解码。
|
||||
|
||||
任何的RPC都需要通过网络来传递数据,Go RPC可以利用HTTP和TCP来传递数据,利用HTTP的好处是可以直接复用`net/http`里面的一些函数。详细的例子请看下面的实现
|
||||
|
||||
###HTTP RPC
|
||||
### HTTP RPC
|
||||
http的服务端代码实现如下:
|
||||
|
||||
package main
|
||||
@@ -145,7 +145,7 @@ http的服务端代码实现如下:
|
||||
Arith: 17/8=2 remainder 1
|
||||
|
||||
通过上面的调用可以看到参数和返回值是我们定义的struct类型,在服务端我们把它们当做调用函数的参数的类型,在客户端作为`client.Call`的第2,3两个参数的类型。客户端最重要的就是这个Call函数,它有3个参数,第1个要调用的函数的名字,第2个是要传递的参数,第3个要返回的参数(注意是指针类型),通过上面的代码例子我们可以发现,使用Go的RPC实现相当的简单,方便。
|
||||
###TCP RPC
|
||||
### TCP RPC
|
||||
上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示:
|
||||
|
||||
package main
|
||||
@@ -263,7 +263,7 @@ http的服务端代码实现如下:
|
||||
|
||||
这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。
|
||||
|
||||
###JSON RPC
|
||||
### JSON RPC
|
||||
JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介绍的RPC概念一模一样,下面我们来演示一下,如何使用Go提供的json-rpc标准包,请看服务端代码的实现:
|
||||
|
||||
package main
|
||||
@@ -379,7 +379,7 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介
|
||||
|
||||
}
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
Go已经提供了对RPC的良好支持,通过上面HTTP、TCP、JSON RPC的实现,我们就可以很方便的开发很多分布式的Web应用,我想作为读者的你已经领会到这一点。但遗憾的是目前Go尚未提供对SOAP RPC的支持,欣慰的是现在已经有第三方的开源实现了。
|
||||
|
||||
|
||||
|
||||
2
8.5.md
2
8.5.md
@@ -1,4 +1,4 @@
|
||||
#8.5 小结
|
||||
# 8.5 小结
|
||||
这一章我们介绍了目前流行的几种主要的网络应用开发方式,第一小节介绍了网络编程中的基础:Socket编程,因为现在网络正在朝云的方向快速进化,作为这一技术演进的基石的的socket知识,作为开发者的你,是必须要掌握的。第二小节介绍了正愈发流行的HTML5中一个重要的特性WebSocket,通过它,服务器可以实现主动的push消息,以简化以前ajax轮询的模式。第三小节介绍了REST编写模式,这种模式特别适合来开发网络应用API,目前移动应用的快速发展,我觉得将来会是一个潮流。第四小节介绍了Go实现的RPC相关知识,对于上面四种开发方式,Go都已经提供了良好的支持,net包及其子包,是所有涉及到网络编程的工具的所在地。如果你想更加深入的了解相关实现细节,可以尝试阅读这个包下面的源码。
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
2
8.md
2
8.md
@@ -1,4 +1,4 @@
|
||||
#8 Web服务
|
||||
# 8 Web服务
|
||||
Web服务可以让你在HTTP协议的基础上通过XML或者JSON来交换信息。如果你想知道上海的天气预报、中国石油的股价或者淘宝商家的一个商品信息,你可以编写一段简短的代码,通过抓取这些信息然后通过标准的接口开放出来,就如同你调用一个本地函数并返回一个值。
|
||||
|
||||
Web服务背后的关键在于平台的无关性,你可以运行你的服务在Linux系统,可以与其他Window的asp.net程序交互,同样的,也可以通过同一个接口和运行在FreeBSD上面的JSP无障碍地通信。
|
||||
|
||||
8
9.1.md
8
9.1.md
@@ -1,13 +1,13 @@
|
||||
# 9.1 预防CSRF攻击
|
||||
|
||||
##什么是CSRF
|
||||
## 什么是CSRF
|
||||
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
|
||||
|
||||
那么CSRF到底能够干嘛呢?你可以这样简单的理解:攻击者可以盗用你的登陆信息,以你的身份模拟发送各种请求。攻击者只要借助少许的社会工程学的诡计,例如通过QQ等聊天软件发送的链接(有些还伪装成短域名,用户无法分辨),攻击者就能迫使Web应用的用户去执行攻击者预设的操作。例如,当用户登录网络银行去查看其存款余额,在他没有退出时,就点击了一个QQ好友发来的链接,那么该用户银行帐户中的资金就有可能被转移到攻击者指定的帐户中。
|
||||
|
||||
所以遇到CSRF攻击时,将对终端用户的数据和操作指令构成严重的威胁;当受攻击的终端用户具有管理员帐户的时候,CSRF攻击将危及整个Web应用程序。
|
||||
|
||||
##CSRF的原理
|
||||
## CSRF的原理
|
||||
下图简单阐述了CSRF攻击的思想
|
||||
|
||||

|
||||
@@ -27,7 +27,7 @@ CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也
|
||||
|
||||
CSRF攻击主要是因为Web的隐式身份验证机制,Web的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。
|
||||
|
||||
##如何预防CSRF
|
||||
## 如何预防CSRF
|
||||
过上面的介绍,读者是否觉得这种攻击很恐怖,意识到恐怖是个好事情,这样会促使你接着往下看如何改进和防止类似的漏洞出现。
|
||||
|
||||
CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。
|
||||
@@ -82,7 +82,7 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从
|
||||
|
||||
这样基本就实现了安全的POST,但是也许你会说如果破解了token的算法呢,按照理论上是,但是实际上破解是基本不可能的,因为有人曾计算过,暴力破解该串大概需要2的11次方时间。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
跨站请求伪造,即CSRF,是一种非常危险的Web安全威胁,它被Web安全界称为“沉睡的巨人”,其威胁程度由此“美誉”便可见一斑。本小节不仅对跨站请求伪造本身进行了简单介绍,还详细说明造成这种漏洞的原因所在,然后以此提了一些防范该攻击的建议,希望对读者编写安全的Web应用能够有所启发。
|
||||
|
||||
## links
|
||||
|
||||
8
9.2.md
8
9.2.md
@@ -7,12 +7,12 @@
|
||||
- 2、过滤数据,弄明白我们需要什么样的数据
|
||||
- 3、区分已过滤及被污染数据,如果存在攻击数据那么保证过滤之后可以让我们使用更安全的数据
|
||||
|
||||
##识别数据
|
||||
## 识别数据
|
||||
“识别数据”作为第一步是因为在你不知道“数据是什么,它来自于哪里”的前提下,你也就不能正确地过滤它。这里的数据是指所有源自非代码内部提供的数据。例如:所有来自客户端的数据,但客户端并不是唯一的外部数据源,数据库和第三方提供的接口数据等也可以是外部数据源。
|
||||
|
||||
由用户输入的数据我们通过Go非常容易识别,Go通过`r.ParseForm`之后,把用户POST和GET的数据全部放在了`r.Form`里面。其它的输入要难识别得多,例如,`r.Header`中的很多元素是由客户端所操纵的。常常很难确认其中的哪些元素组成了输入,所以,最好的方法是把里面所有的数据都看成是用户输入。(例如`r.Header.Get("Accept-Charset")`这样的也看做是用户输入,虽然这些大多数是浏览器操纵的)
|
||||
|
||||
##过滤数据
|
||||
## 过滤数据
|
||||
在知道数据来源之后,就可以过滤它了。过滤是一个有点正式的术语,它在平时表述中有很多同义词,如验证、清洁及净化。尽管这些术语表面意义不同,但它们都是指的同一个处理:防止非法数据进入你的应用。
|
||||
|
||||
过滤数据有很多种方法,其中有一些安全性较差。最好的方法是把过滤看成是一个检查的过程,在你使用数据之前都检查一下看它们是否是符合合法数据的要求。而且不要试图好心地去纠正非法数据,而要让用户按你制定的规则去输入数据。历史证明了试图纠正非法数据往往会导致安全漏洞。这里举个例子:“最近建设银行系统升级之后,如果密码后面两位是0,只要输入前面四位就能登录系统”,这是一个非常严重的漏洞。
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
过滤数据除了检查验证之外,在特殊时候,还可以采用白名单。即假定你正在检查的数据都是非法的,除非能证明它是合法的。使用这个方法,如果出现错误,只会导致把合法的数据当成是非法的,而不会是相反,尽管我们不想犯任何错误,但这样总比把非法数据当成合法数据要安全得多。
|
||||
|
||||
##区分过滤数据
|
||||
## 区分过滤数据
|
||||
如果完成了上面的两步,数据过滤的工作就基本完成了,但是在编写Web应用的时候我们还需要区分已过滤和被污染数据,因为这样可以保证过滤数据的完整性,而不影响输入的数据。我们约定把所有经过过滤的数据放入一个叫全局的Map变量中(CleanMap)。这时需要用两个重要的步骤来防止被污染数据的注入:
|
||||
- 每个请求都要初始化CleanMap为一个空Map。
|
||||
- 加入检查及阻止来自外部数据源的变量命名为CleanMap。
|
||||
@@ -63,7 +63,7 @@
|
||||
CleanMap["username"] = username
|
||||
}
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
数据过滤在Web安全中起到一个基石的作用,大多数的安全问题都是由于没有过滤数据和验证数据引起的,例如前面小节的CSRF攻击,以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的,因此我们需要特别重视这部分的内容。
|
||||
|
||||
## links
|
||||
|
||||
8
9.3.md
8
9.3.md
@@ -1,7 +1,7 @@
|
||||
# 9.3 避免XSS攻击
|
||||
随着互联网技术的发展,现在的Web应用都含有大量的动态内容以提高用户体验。所谓动态内容,就是应用程序能够根据用户环境和用户请求,输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响。
|
||||
|
||||
##什么是XSS
|
||||
## 什么是XSS
|
||||
XSS攻击:跨站脚本攻击(Cross-Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。XSS是一种常见的web安全漏洞,它允许攻击者将恶意代码植入到提供给其它用户使用的页面中。不同于大多数攻击(一般只涉及攻击者和受害者),XSS涉及到三方,即攻击者、客户端与Web应用。XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互。
|
||||
|
||||
XSS通常可以分为两大类:一类是存储型XSS,主要出现在让用户输入数据,供其他浏览此页的用户进行查看的地方,包括留言、评论、博客日志和各类表单等。应用程序从数据库中查询数据,在页面中显示出来,攻击者在相关页面输入恶意的脚本数据后,用户浏览此类页面时就可能受到攻击。这个流程简单可以描述为:恶意用户的Html输入Web程序->进入数据库->Web程序->用户浏览器。另一类是反射型XSS,主要做法是将脚本代码加入URL地址的请求参数里,请求参数进入程序后在页面直接输出,用户点击类似的恶意链接就可能受到攻击。
|
||||
@@ -14,7 +14,7 @@ XSS目前主要的手段和目的如下:
|
||||
- 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
|
||||
- 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果
|
||||
|
||||
##XSS的原理
|
||||
## XSS的原理
|
||||
Web应用未对用户提交请求的数据做充分的检查过滤,允许用户在提交的数据中掺入HTML代码(最主要的是“>”、“<”),并将未经转义的恶意代码输出到第三方用户的浏览器解释执行,是导致XSS漏洞的产生原因。
|
||||
|
||||
接下来以反射性XSS举例说明XSS的过程:现在有一个网站,根据参数输出用户的名称,例如访问url:`http://127.0.0.1/?name=astaxie`,就会在浏览器输出如下信息:
|
||||
@@ -25,7 +25,7 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用
|
||||
|
||||
更加详细的关于XSS的分析大家可以参考这篇叫做《[新浪微博XSS事件分析](http://www.rising.com.cn/newsletter/news/2011-08-18/9621.html)》的文章
|
||||
|
||||
##如何预防XSS
|
||||
## 如何预防XSS
|
||||
答案很简单,坚决不要相信用户的任何输入,并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的XSS攻击。
|
||||
|
||||
目前防御XSS主要有如下几种方式:
|
||||
@@ -43,7 +43,7 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用
|
||||
这样就可以让浏览器解析javascript代码,而不会是html输出。
|
||||
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
XSS漏洞是相当有危害的,在开发Web应用的时候,一定要记住过滤数据,特别是在输出到客户端之前,这是现在行之有效的防止XSS的手段。
|
||||
|
||||
## links
|
||||
|
||||
8
9.4.md
8
9.4.md
@@ -1,9 +1,9 @@
|
||||
# 9.4 避免SQL注入
|
||||
##什么是SQL注入
|
||||
## 什么是SQL注入
|
||||
SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。
|
||||
|
||||
而造成SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。
|
||||
##SQL注入实例
|
||||
## SQL注入实例
|
||||
很多Web开发者没有意识到SQL查询是可以被篡改的,从而把SQL查询当作可信任的命令。殊不知,SQL查询是可以绕开访问控制,从而绕过身份验证和权限检查的。更有甚者,有可能通过SQL查询去运行主机系统级的命令。
|
||||
|
||||
下面将通过一些真实的例子来详细讲解SQL注入的方式。
|
||||
@@ -46,7 +46,7 @@ MSSQL服务器会执行这条SQL语句,包括它后面那个用于向系统添
|
||||
>虽然以上的例子是针对某一特定的数据库系统的,但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞,只要使用不同方法,各种数据库都有可能遭殃。
|
||||
|
||||
|
||||
##如何预防SQL注入
|
||||
## 如何预防SQL注入
|
||||
也许你会说攻击者要知道数据库结构的信息才能实施SQL注入攻击。确实如此,但没人能保证攻击者一定拿不到这些信息,一旦他们拿到了,数据库就存在泄露的危险。如果你在用开放源代码的软件包来访问数据库,比如论坛程序,攻击者就很容易得到相关的代码。如果这些代码设计不良的话,风险就更大了。目前Discuz、phpwind、phpcms等这些流行的开源程序都有被SQL注入攻击的先例。
|
||||
|
||||
这些攻击总是发生在安全性不高的代码上。所以,永远不要信任外界输入的数据,特别是来自于用户的数据,包括选择框、表单隐藏域和 cookie。就如上面的第一个例子那样,就算是正常的查询也有可能造成灾难。
|
||||
@@ -60,7 +60,7 @@ SQL注入攻击的危害这么大,那么该如何来防治呢?下面这些建
|
||||
5. 在应用发布之前建议使用专业的SQL注入检测工具进行检测,以及时修补被发现的SQL注入漏洞。网上有很多这方面的开源工具,例如sqlmap、SQLninja等。
|
||||
6. 避免网站打印出SQL错误信息,比如类型错误、字段不匹配等,把代码里的SQL语句暴露出来,以防止攻击者利用这些错误信息进行SQL注入。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
通过上面的示例我们可以知道,SQL注入是危害相当大的安全漏洞。所以对于我们平常编写的Web应用,应该对于每一个小细节都要非常重视,细节决定命运,生活如此,编写Web应用也是这样。
|
||||
|
||||
## links
|
||||
|
||||
8
9.5.md
8
9.5.md
@@ -3,7 +3,7 @@
|
||||
|
||||
那么我们作为一个Web应用开发者,在选择密码存储方案时, 容易掉入哪些陷阱, 以及如何避免这些陷阱?
|
||||
|
||||
##普通方案
|
||||
## 普通方案
|
||||
目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256, SHA-1, MD5等。
|
||||
|
||||
Go语言对这三种加密算法的实现如下所示:
|
||||
@@ -31,7 +31,7 @@ Go语言对这三种加密算法的实现如下所示:
|
||||
结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为`rainbow table`。
|
||||
|
||||
因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。
|
||||
##进阶方案
|
||||
## 进阶方案
|
||||
通过上面介绍我们知道黑客可以用`rainbow table`来破解哈希后的密码,很大程度上是因为加密时使用的哈希算法是公开的。如果黑客不知道加密的哈希算法是什么,那他也就无从下手了。
|
||||
|
||||
一个直接的解决办法是,自己设计一个哈希算法。然而,一个好的哈希算法是很难设计的——既要避免碰撞,又不能有明显的规律,做到这两点要比想象中的要困难很多。因此实际应用中更多的是利用已有的哈希算法进行多次哈希。
|
||||
@@ -62,7 +62,7 @@ Go语言对这三种加密算法的实现如下所示:
|
||||
|
||||
在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。
|
||||
|
||||
##专家方案
|
||||
## 专家方案
|
||||
上面的进阶方案在几年前也许是足够安全的方案,因为攻击者没有足够的资源建立这么多的`rainbow table`。 但是,时至今日,因为并行计算能力的提升,这种攻击已经完全可行。
|
||||
|
||||
怎么解决这个问题呢?只要时间与资源允许,没有破译不了的密码,所以方案是:故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的`rainbow table`。
|
||||
@@ -77,7 +77,7 @@ Go语言对这三种加密算法的实现如下所示:
|
||||
|
||||
通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
看到这里,如果你产生了危机感,那么就行动起来:
|
||||
|
||||
- 1)如果你是普通用户,那么我们建议使用LastPass进行密码存储和生成,对不同的网站使用不同的密码;
|
||||
|
||||
6
9.6.md
6
9.6.md
@@ -1,7 +1,7 @@
|
||||
# 9.6 加密和解密数据
|
||||
前面小节介绍了如何存储密码,但是有的时候,我们想把一些敏感数据加密后存储起来,在将来的某个时候,随需将它们解密出来,此时我们应该在选用对称加密算法来满足我们的需求。
|
||||
|
||||
##base64加解密
|
||||
## base64加解密
|
||||
如果Web应用足够简单,数据的安全性没有那么严格的要求,那么可以采用一种比较简单的加解密方法是`base64`,这种方式实现起来比较简单,Go语言的`base64`包已经很好的支持了这个,请看下面的例子:
|
||||
|
||||
package main
|
||||
@@ -43,7 +43,7 @@
|
||||
fmt.Println(string(enbyte))
|
||||
}
|
||||
|
||||
##高级加解密
|
||||
## 高级加解密
|
||||
|
||||
Go语言的`crypto`里面支持对称加密的高级加解密包有:
|
||||
|
||||
@@ -106,7 +106,7 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有:
|
||||
|
||||
这三个函数实现了加解密操作,详细的操作请看上面的例子。
|
||||
|
||||
##总结
|
||||
## 总结
|
||||
这小节介绍了几种加解密的算法,在开发Web应用的时候可以根据需求采用不同的方式进行加解密,一般的应用可以采用base64算法,更加高级的话可以采用aes或者des算法。
|
||||
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
||||
#《Go Web 编程》
|
||||
# 《Go Web 编程》
|
||||
因为自己对Web开发比较感兴趣,所以最近抽空在写一本开源的书籍《Go Web编程》《Build Web Application with Golang》。写这本书不表示我能力很强,而是我愿意分享,和大家一起分享Go写Web应用的一些东西。
|
||||
|
||||
- 对于从PHP/Python/Ruby转过来的同学了解Go怎么写Web应用开发的
|
||||
@@ -21,22 +21,22 @@
|
||||
### 代码
|
||||
代码要**`go fmt`**后提交。注释文件注明其所属章节。
|
||||
|
||||
##如何编译
|
||||
## 如何编译
|
||||
`build.go`依赖markdown的一个解析包,所以第一步先
|
||||
|
||||
go get github.com/russross/blackfriday
|
||||
|
||||
这样读者就可以把相应的Markdown文件编译成html文件,执行`go build build.go`,执行生成的文件,就会在底目录下生成相应的html文件
|
||||
|
||||
##如何编译
|
||||
## 如何编译
|
||||
目前可以把相应的Markdown编译成html文件,执行`go build build.go`,执行生成的文件,就会在底目录下生成相应的html文件。
|
||||
|
||||
##交流
|
||||
## 交流
|
||||
欢迎大家加入QQ群:259316004 《Go Web编程》专用交流群
|
||||
|
||||
大家有问题还可以上德问上一起交流学习:http://www.dewen.org/topic/165
|
||||
|
||||
##致谢
|
||||
## 致谢
|
||||
首先要感谢Golang-China的QQ群102319854,里面的每一个人都很热心,同时要特别感谢几个人
|
||||
|
||||
- [四月份平民](https://plus.google.com/110445767383269817959) (review代码)
|
||||
@@ -45,10 +45,10 @@
|
||||
- [Oling Cat](https://github.com/OlingCat)(review代码)
|
||||
- [Wenlei Wu](mailto:spadesacn@gmail.com)(提供一些图片展示)
|
||||
|
||||
##授权许可
|
||||
## 授权许可
|
||||
除特别声明外,本书中的内容使用[CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/)(创作共用 署名-相同方式共享3.0许可协议)授权,代码遵循[BSD 3-Clause License](<https://github.com/astaxie/build-web-application-with-golang/blob/master/LICENSE.md>)(3项条款的BSD许可协议)。
|
||||
|
||||
##开始阅读
|
||||
## 开始阅读
|
||||
[开始阅读](<https://github.com/astaxie/build-web-application-with-golang/blob/master/preface.md>)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user