update some words in 4.*md and 5.1md

This commit is contained in:
chenwenli
2012-11-04 22:17:35 +08:00
parent f7b3d6cd51
commit 27ed9d7493
6 changed files with 55 additions and 48 deletions

18
4.1.md
View File

@@ -15,9 +15,9 @@
</body>
</html>
上面递交表单到/login里面,当用户输入信息之后点击登陆之后,跳转到我们的login里面我们首先要判断这个是什么方式过来post还是get呢?
上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢?
http包里面有一个很简单的方式就可以获取我们在前面web的例子基础上来看看怎么处理login,怎么读取form数据
http包里面有一个很简单的方式就可以获取我们在前面web的例子基础上来看看怎么处理login页面的form数据
package main
@@ -31,7 +31,8 @@ http包里面有一个很简单的方式就可以获取我们在前面web的
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不会解析的
r.ParseForm() //解析url传递的参数对于POST则解析响应包的主体request body
//注意:如果没有调用parseForm方法下面无法获取表单的数据
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
@@ -69,19 +70,19 @@ http包里面有一个很简单的方式就可以获取我们在前面web的
login函数中我们根据`r.Method`来判断是显示登录界面还是处理登录逻辑。当GET方式请求时显示登录界面其他方式请求时则处理登录逻辑如查询数据库、验证登录信息等。
当我们在浏览器里面输入`http://127.0.0.1:9090/login`的时候,出现如下界面
当我们在浏览器里面打开`http://127.0.0.1:9090/login`的时候,出现如下界面
![](images/4.1.login.png?raw=true)
我们输入用户名和密码之后发现在服务器端是不会打印出来任何东西为什么呢默认情况下Handler里面是不会自动解析form的必须显式的调用`r.ParseForm()`后,你才能对这个参数进行操作。我们修改一下代码,在`fmt.Println("username:", r.Form["username"])`之前加一行`r.ParseForm()`,重新编译,再次测试输入递交,现在是不是在服务器端有输出你的输入的用户名和密码了。
我们输入用户名和密码之后发现在服务器端是不会打印出来任何输出为什么呢默认情况下Handler里面是不会自动解析form的必须显式的调用`r.ParseForm()`后,你才能对这个表单数据进行操作。我们修改一下代码,在`fmt.Println("username:", r.Form["username"])`之前加一行`r.ParseForm()`,重新编译,再次测试输入递交,现在是不是在服务器端有输出你的输入的用户名和密码了。
`r.Form`里面包含了所有请求的URL中query-string、POST的数据、PUT的数据所有当你在URL的query-string字段和POST冲突时会保存成一个slice里面存储了多个值Go官方文档中说在接下来的版本里面将会把POST、GET这些数据分离开来。
`r.Form`里面包含了所有请求的参数,比如URL中query-string、POST的数据、PUT的数据所有当你在URL的query-string字段和POST冲突时会保存成一个slice里面存储了多个值Go官方文档中说在接下来的版本里面将会把POST、GET这些数据分离开来。
现在我们修改一下login.gtpl里面form的action值`http://127.0.0.1:9090/login`修改为`http://127.0.0.1:9090/login?username=astaxie`再次测试服务器的输出username是不是一个slice。服务器端的输出如下
![](images/4.1.slice.png?raw=true)
`r.Form`是一个url.Values类型里面存储的是对应的key=value信息下面展示了可以对form进行的一些操作:
`request.Form`是一个url.Values类型里面存储的是对应的类似`key=value`信息下面展示了可以对form数据进行的一些操作:
v := url.Values{}
v.Set("name", "Ava")
@@ -93,7 +94,8 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])
Tips: Request请求也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm所以不必提前调用。r.FormValue只会返回同名参数中的第一个若参数不存在则返回空字符串。
>**Tips**:
Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm所以不必提前调用。r.FormValue只会返回同名参数中的第一个若参数不存在则返回空字符串。
## links
* [目录](<preface.md>)

10
4.2.md
View File

@@ -1,11 +1,11 @@
# 4.2验证表单的输入
我们开发Web的一个原则就是不能信任用户输入的任何信息所以验证和过滤用户的输入信息就变得相当重要我们经常会在微博、新闻中听到某某网站被入侵了存在什么漏洞这些大多是是因为对于用户输入的信息没有做严格的验证引起的所以为了编写出安全可靠的Web程序验证表单输入变得相当的重要
我们开发Web的一个原则就是不能信任用户输入的任何信息所以验证和过滤用户的输入信息就变得非常重要,我们经常会在微博、新闻中听到某某网站被入侵了,存在什么漏洞,这些大多是是因为网站对于用户输入的信息没有做严格的验证引起的所以为了编写出安全可靠的Web程序验证表单输入的意义重大
我们平常编写Web应用主要有两方面的数据验证一个是在页面端的js验证(目前很多这方面的验证库),一个是在服务器端的验证,我们这小节讲解的是如何在服务器端验证。
我们平常编写Web应用主要有两方面的数据验证一个是在页面端的js验证(目前这方面有很多的插件库比如ValidationJS插件),一个是在服务器端的验证,我们这小节讲解的是如何在服务器端验证。
## 必填字段
你想要确保从一个表单元素中得到一个值例如前面小节里面的用户名我们如何处理呢Go有一个内置函数`len`可以获取字符串的长度这样我们就可以通过len来测试获取数据的长度,例如:
你想要确保从一个表单元素中得到一个值例如前面小节里面的用户名我们如何处理呢Go有一个内置函数`len`可以获取字符串的长度这样我们就可以通过len来获取数据的长度例如
if len(r.Form["username"][0])==0{
//为空的处理
@@ -133,14 +133,14 @@
## 日期和时间
你想确定用户填写的日期或时间是否有效。例如
你想确保用户在日程表中安排8月份的第45天开会或者提供还没到的时间作为生日。
用户在日程表中安排8月份的第45天开会或者提供未来的某个时间作为生日。
Go里面提供了一个time的处理包我们可以把用户的输入年月日转化成相应的时间然后进行逻辑判断
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Printf("Go launched at %s\n", t.Local())
获取时间之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。
获取time之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。
## 身份证号码
如果我们想验证表单输入的是否是身份证通过正则也可以方便的验证但是身份证有15位和18位我们两个都需要验证

8
4.3.md
View File

@@ -2,9 +2,9 @@
现在的网站包含大量的动态内容以提高用户体验比过去要复杂得多。所谓动态内容就是根据用户环境和需要Web应用程序能够输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”Cross Site Scripting, 安全专家们通常将其缩写成 XSS的威胁而静态站点则完全不受其影响。
攻击者通常会在有漏洞的程序中插入JavaScript、VBScript、 ActiveX或Flash以欺骗用户。一旦得手他们可以盗取用户帐户修改用户设置盗取/污染cookie,做虚假广告等。
攻击者通常会在有漏洞的程序中插入JavaScript、VBScript、 ActiveX或Flash以欺骗用户。一旦得手他们可以盗取用户帐户信息,修改用户设置,盗取/污染cookie和植入恶意广告等。
对XSS最佳的防护应该结合以下两种方法验证所有输入数据有效检测攻击(这个我们前面小节已经有过介绍);对所有输出数据进行适当的编码,以防止任何已成功注入的脚本在浏览器端运行。
对XSS最佳的防护应该结合以下两种方法一是验证所有输入数据,有效检测攻击(这个我们前面小节已经有过介绍);另一个是对所有输出数据进行适当的处理,以防止任何已成功注入的脚本在浏览器端运行。
那么Go里面是怎么做这个有效防护的呢Go的html/template里面带有下面几个函数可以帮你转义
@@ -23,7 +23,7 @@
![](images/4.3.escape.png?raw=true)
那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包默认帮你过滤了html元素,但是有时候你又想输出这样的信息,请使用text/template。请看下面的例子所示
Go的html/template包默认帮你过滤了html标签,但是有时候你只想要输出这个`<script>alert()</script>`看起来正常的信息,该怎么处理?请使用text/template。请看下面的例子
import "text/template"
...
@@ -45,7 +45,7 @@
Hello, <script>alert('you have been pwned')</script>!
转换成template.HTML后变量的内容也不会被转义
转换成`template.HTML`后,变量的内容也不会被转义
转义的例子:

24
4.5.md
View File

@@ -1,13 +1,13 @@
# 4.5处理文件上传
你想处理一个由用户上传的文件比如你正在建设一个类似Instagram的网站你需要存储用户拍摄的照片。这种需求该如何实现呢
你想处理一个由用户上传的文件比如你正在建设一个类似Instagram的网站你需要存储用户拍摄的照片。这种需求该如何实现呢
文件要能够上传,首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
使表单能够上传文件,首先第一步就是要添加form的`enctype`属性,`enctype`属性有如下三种情况:
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
如果要支持文件上传,我们的html应该类似于:
所以,表单的html代码应该类似于:
<html>
<head>
@@ -15,8 +15,8 @@
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
<input type="file" name="file" />
<input type="hidden" name="token" value="{{.}}">
<input type="file" name="uploadfile" />
<input type="hidden" name="token" value="{{.}}"/>
<input type="submit" value="upload" />
</form>
</body>
@@ -26,7 +26,7 @@
http.HandleFunc("/upload", upload)
// upload
// 处理/upload 逻辑
func upload(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) //获取请求的方法
if r.Method == "GET" {
@@ -39,7 +39,7 @@
t.Execute(w, token)
} else {
r.ParseMultipartForm(32 << 20)
file, handler, err := r.FormFile("file")
file, handler, err := r.FormFile("uploadfile")
if err != nil {
fmt.Println(err)
return
@@ -62,9 +62,9 @@
通过上面的实例我们可以看到我们上传文件主要三步处理:
- 1、表单中增加enctype="multipart/form-data"
- 2、服务端调用`r.ParseMultipartForm`,把上传的文件存储在内存和临时文件中
- 3、使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
1. 表单中增加enctype="multipart/form-data"
2. 服务端调用`r.ParseMultipartForm`,把上传的文件存储在内存和临时文件中
3. 使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
文件handler是multipart.FileHeader,里面存储了如下结构信息
@@ -100,7 +100,7 @@
bodyWriter := multipart.NewWriter(bodyBuf)
//关键的一步操作
fileWriter, err := bodyWriter.CreateFormFile("file", filename)
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
if err != nil {
fmt.Println("error writing to buffer")
return err
@@ -144,7 +144,7 @@
}
上面的例子详细展示了如何上传一个文件,客户端上传文件通过multipartWrite把文件信息写入缓存然后调用http的post方法上传文件
上面的例子详细展示了客户端如何向服务器上传一个文件的例子客户端通过multipart.Write把文件的文本流写入一个缓存然后调用http的Post方法把缓存传到服务器
>如果你还有其他普通字段例如username之类的需要同时写入那么可以调用multipart的WriteField方法写很多其他类似的字段。

4
4.6.md
View File

@@ -1,7 +1,7 @@
# 4.6 小结
这一章里面我们学习了Go里面如何处理表单信息,我们通过一个登陆、一个上传例子展示了Go处理form表单信息,处理文件上传的能力。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全数据过滤就得相当重要了,因此专门一个小节讲解了方面的数据过滤,顺带讲一下Go里面对正则处理。
这一章里面我们学习了Go如何处理表单信息我们通过用户登陆、上传文件的例子展示了Go处理form表单信息及上传文件的手段。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全的重要性,数据过滤就得相当重要了,因此后面的章节中专门写了一个小节讲解了不同方面的数据过滤顺带讲一下Go对字符串的正则处理。
通过这一章能够让你了解客户端和服务器端如何进行数据的交互,客户端数据进入我们的系服务器统,让我们系统处理之后的数据展现给客户端。
通过这一章能够让你了解客户端和服务器端如何进行数据的交互,客户端数据传递给服务器系统,服务器接受数据又把处理结果反馈给客户端。
## links
* [目录](<preface.md>)

39
5.1.md
View File

@@ -1,5 +1,5 @@
# 5.1 database/sql接口
GoPHP不同的地方是,他没有官方提供数据库驱动,而是为开发数据库驱动定义了一些标准接口,第三方用户可以根据定义的接口来开发相应的数据库驱动,这样做有一个好处,我们按照标准接口开发的代码, 需要迁移数据库时不需要任何修改。那么Go都定义了些标准接口呢?让我们来详细的分析一下
GoPHP不同的地方是Go没有官方提供数据库驱动,而是为开发者开发数据库驱动定义了一些标准接口,开发者可以根据定义的接口来开发相应的数据库驱动,这样做有一个好处,只要按照标准接口开发的代码, 以后需要迁移数据库时不需要任何修改。那么Go都定义了些标准接口呢?让我们来详细的分析一下
## sql.Register
这个存在于database/sql的函数是用来注册数据库驱动的当第三方开发者开发数据库驱动时都会实现init函数在init里面会调用这个`Register(name string, driver driver.Driver)`完成本驱动的注册。
@@ -19,7 +19,7 @@ Go和PHP不同的地方是他没有官方提供数据库驱动而是为开
sql.Register("mymysql", &d)
}
我们看到第三方驱动都是通过调用这个函数来注册自己的驱动名称以及相应的driver。在database/sql内部通过一个map来存储相应驱动。
我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。
var drivers = make(map[string]driver.Driver)
@@ -27,12 +27,16 @@ Go和PHP不同的地方是他没有官方提供数据库驱动而是为开
因此通过database/sql的注册函数可以同时注册多个数据库驱动只要不重复。
>在我们使用database/sql接口和第三方库的时候经常看到如下的import
>在我们使用database/sql接口和第三方库的时候经常看到如下:
"database/sql"
_ "github.com/mattn/go-sqlite3"
> import (
> "database/sql"
> _ "github.com/mattn/go-sqlite3"
> )
>新手都会被这个`_`所迷惑其实这个就是Go设计的巧妙之处我们在变量赋值的时候经常看到这个它是用来忽略变量的占位符那么这个包引入也是,这儿的意思是引入此包而不直接使用这个包中定义的函数,变量等资源我们在2.3流程和函数里面介绍过init函数的初始化过程包在引入的时候会去调用包的init函数以完成对包的初始化因此我们引入上面的数据库驱动包之后会去调用init函数然后在init函数里面注册了这个数据库驱动这样我们就可以在接下来的代码中直接使用这个数据库驱动了
>新手都会被这个`_`所迷惑其实这个就是Go设计的巧妙之处我们在变量赋值的时候经常看到这个符号,它是用来忽略变量赋值的占位符,那么包引入用到这个符号也是相似的作用,这儿使用`_`的意思是引入后面的包名而不直接使用这个包中定义的函数,变量等资源。
>我们在2.3节流程和函数的一节中介绍过init函数的初始化过程包在引入的时候会自动调用包的init函数以完成对包的初始化。因此我们引入上面的数据库驱动包之后要手动去调用init函数然后在init函数里面注册这个数据库驱动这样我们就可以在接下来的代码中直接使用这个数据库驱动了。
## driver.Driver
Driver是一个数据库驱动的接口他定义了一个method Open(name string)这个方法返回一个数据库的Conn接口。
@@ -48,7 +52,7 @@ Driver是一个数据库驱动的接口他定义了一个method Open(name
go goroutineB (Conn) //执行插入操作
...
上面这样的代码可能会导致不知道某个操作究竟是由哪个goroutine发起的,从而数据混乱比如可能会把goroutine A里面执行的查询操作的结果返回给goroutine B从而使B错误把此结果当成自己执行的插入结果
上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误把此结果当成自己执行的插入数据
第三方驱动都会定义这个函数它会解析name参数来获取相关数据库的连接信息解析完成后它将使用此信息来初始化一个Conn并返回它。
@@ -61,14 +65,14 @@ Conn是一个数据库连接的接口定义他定义了一系列方法
Begin() (Tx, error)
}
Prepare函数返回与当前连接相关的准备好Sql语句的状态可以进行查询、删除等操作。
Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。
Close函数关闭当前的连接以及执行释放连接拥有的资源等清理工作。因为database/sql里面实现了建议的conn pool所以你不要再自己去实现缓存conn之类的这样容易引起问题。
Close函数关闭当前的连接执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool所以你不用再去实现缓存conn之类的这样容易引起问题。
Begin函数返回一个代表事务处理的Tx通过它你可以进行查询,更新等操作,或者对事务进行回滚、递交。
## driver.Stmt
Stmt是一种准备好的状态和Conn相关联而且只能应用于一个goroutine中不能应用多个goroutine
Stmt是一种准备好的状态和Conn相关联而且只能应用于一个goroutine中不能应用多个goroutine。
type Stmt interface {
Close() error
@@ -113,7 +117,7 @@ Query函数执行Prepare准备好的sql传入需要的参数执行select操
RowsAffected() (int64, error)
}
LastInsertId函数返回由数据库执行插入操作得到的自动增长ID号。
LastInsertId函数返回由数据库执行插入操作得到的自ID号。
RowsAffected函数返回query操作影响的数据条目数。
@@ -126,7 +130,7 @@ Rows是执行查询返回的结果集接口定义
Next(dest []Value) error
}
Columns函数返回查询数据库表的字段信息这个返回的slice和sql查询的字段一一对应而不是返回整个表的所有字段。
Columns函数返回查询数据库表的字段信息这个返回的slice和sql查询的字段一一对应而不是返回整个表的所有字段。
Close函数用来关闭Rows迭代器。
@@ -147,7 +151,7 @@ Value其实就是一个空接口他可以容纳任何的数据
type Value interface{}
Value的值必须所有的驱动里面控制的Value要么是nil要么是下面的任意一种
drive的Value是驱动必须能够操作的ValueValue要么是nil要么是下面的任意一种
int64
float64
@@ -175,22 +179,23 @@ Valuer接口定义了返回一个driver.Value的方式
type Valuer interface {
Value() (Value, error)
}
很多类型都实现了这个Value方法用来自身与driver.Value的转化。
通过上面的讲解,你应该对于驱动的开发有了一个基本的了解,一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了,在此不再赘述。
## database/sql
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法用以简化数据库操作,同时内部还实现一个建议的conn pool。
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法用以简化数据库操作,同时内部还建议性地实现一个conn pool。
type DB struct {
driver driver.Driver
dsn string
driver driver.Driver
dsn string
mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn
closed bool
}
我们可以看到Open函数返回的是DB对象里面有一个freeConn它就是那个简易的连接池。它的实现相当简单或者说简陋就是当执行Db.prepare的时候会defer:db.putConn(ci, err),也就是放入连接池每次调用conn的时候会先判断freeConn的长度是否大于0大于0说明有可以复用的conn直接拿出来用就是了如果不大于0则创建一个conn,然后再返回之。
我们可以看到Open函数返回的是DB对象里面有一个freeConn它就是那个简易的连接池。它的实现相当简单或者说简陋就是当执行Db.prepare的时候会`defer db.putConn(ci, err)`,也就是把这个连接放入连接池每次调用conn的时候会先判断freeConn的长度是否大于0大于0说明有可以复用的conn直接拿出来用就是了如果不大于0则创建一个conn,然后再返回之。
## links