Merge pull request #754 from vCaesar/tool-pr

Fix Sublime and Add Atom & vscode &Goglang  and Add syntax highlighting
This commit is contained in:
astaxie
2016-12-19 10:50:38 +08:00
committed by GitHub
59 changed files with 1052 additions and 329 deletions

View File

@@ -91,7 +91,7 @@ LiteIDE features.
## Sublime Text
Here I'm going to introduce you the Sublime Text 2 (Sublime for short) + GoSublime + gocode + MarGo. Let me explain why.
Here I'm going to introduce you the Sublime Text 3 (Sublime for short) + GoSublime + gocode. Let me explain why.
- Intelligent completion
@@ -112,7 +112,16 @@ First, download the version of [Sublime](http://www.sublimetext.com/) suitable f
1. Press ``Ctrl+` ``, open the command tool and input the following commands.
import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation'
Applicable to Sublime Text 3
```Go
import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read())
```
Applicable to Sublime Text 2
```Go
import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation')
```
Restart Sublime Text when the installation has finished. You should then find a `Package Control` option in the "Preferences" menu.
@@ -399,6 +408,20 @@ This is an awesome text editor released as open source cross platform my Microso
It works with Windows, Mac, Linux. It has go package built, it provides code linting.
## Atom
Atom is an awesome text editor released as open source cross platform, built on Electron , and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration.
Download: https://atom.io/
##Goglang
Gogland is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development.
The official version is not yet released。
Download:https://www.jetbrains.com/go/
## Links
- [Directory](preface.md)

View File

@@ -1,6 +1,6 @@
# 1.5 Summary
In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go.
In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, Goglang, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go.
## Links

View File

@@ -9,14 +9,14 @@ beedb is an open source project that supports basic ORM functionality, but doesn
Because beedb supports `database/sql` interface standards, any driver that implements this interface can be used with beedb. I've tested the following drivers:
Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv)
Mysql: [github/go-mysql-driver/mysql](github.com/go-sql-driver/mysql)
Mysql: [code.google.com/p/go-mysql-driver](code.google.com/p/go-mysql-driver)
PostgreSQL: [github.com/bmizerany/pq](github.com/bmizerany/pq)
PostgreSQL: [github.com/bmizerany/pq](github.com/lib/pq)
SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3)
Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv)
MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb)
ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc)

View File

@@ -9,7 +9,7 @@ As the C language of the 21st century, Go has good support for NoSQL databases,
redis is a key-value storage system like Memcached, that supports the string, list, set and zset(ordered set) value types.
There are some Go database drivers for redis:
- [https://github.com/garyburd/redigo](https://github.com/garyburd/redigo)
- [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis)
- [http://code.google.com/p/tideland-rdc/](http://code.google.com/p/tideland-rdc/)
- [https://github.com/simonz05/godis](https://github.com/simonz05/godis)

View File

@@ -93,7 +93,7 @@
## Sublime Text
这里将介绍Sublime Text 2以下简称Sublime+GoSublime的组合那么为什么选择这个组合呢
这里将介绍Sublime Text 3以下简称Sublime+GoSublime + gocode的组合,那么为什么选择这个组合呢?
- 自动化提示代码,如下图所示
@@ -109,7 +109,7 @@
图1.6 sublime项目管理界面
- 支持语法高亮
- Sublime Text 2可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。
- Sublime Text 3可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。
接下来就开始讲如何安装,下载[Sublime](http://www.sublimetext.com/)
@@ -118,7 +118,16 @@
1. 打开之后安装 Package ControlCtrl+` 打开命令行,执行如下代码:
import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation'
适用于 Sublime Text 3
```Go
import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read())
```
适用于 Sublime Text 2
```Go
import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation')
```
这个时候重启一下Sublime可以发现在在菜单栏多了一个如下的栏目说明Package Control已经安装成功了。
@@ -137,6 +146,22 @@
这个时候输入GoSublime按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。
3. 安装[gocode](https://github.com/nsf/gocode/)
go get -u github.com/nsf/gocode
gocode 将会安装在默认`$GOBIN`
另外建议安装gotests(生成测试代码):
先在sublime安装gotests插件,再运行:
```Go
go get -u -v github.com/cweill/gotests/...
```
3. 验证是否安装成功你可以打开Sublime打开main.go看看语法是不是高亮了输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。
如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。
@@ -153,6 +178,135 @@
ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime
## Visual Studio Code
vscode是微软基于Electron和web技术构建的开源编辑器, 是一款很强大的编辑器。开源地址:https://github.com/Microsoft/vscode
1、安装Visual Studio Code 最新版
官方网站https://code.visualstudio.com/
下载Visual Studio Code 最新版,安装过程略。
2、安装Go插件
点击右边的Extensions图标
搜索Go插件
在插件列表中,选择 Go进行安装安装之后系统会提示重启Visual Studio Code。
建议把自动保存功能开启。开启方法为选择菜单File点击Auto save。
vscode代码设置可用于Go扩展。这些都可以在用户的喜好来设置或工作区设置.vscode/settings.json
打开首选项-用户设置settings.json:
```Go
{
"go.buildOnSave": true,
"go.lintOnSave": true,
"go.vetOnSave": true,
"go.buildFlags": [],
"go.lintFlags": [],
"go.vetFlags": [],
"go.coverOnSave": false,
"go.useCodeSnippetsOnFunctionSuggest": false,
"go.formatOnSave": true,
//goimports
"go.formatTool": "goreturns",
"go.goroot": "",//你的Goroot
"go.gopath": "",//你的Gopath
}
```
接着安装依赖包支持(网络不稳定,请直接到Github[Golang](https://github.com/golang)下载再移动到相关目录):
```Go
go get -u -v github.com/nsf/gocode
go get -u -v github.com/rogpeppe/godef
go get -u -v github.com/zmb3/gogetdoc
go get -u -v github.com/golang/lint/golint
go get -u -v github.com/lukehoban/go-outline
go get -u -v sourcegraph.com/sqs/goreturns
go get -u -v golang.org/x/tools/cmd/gorename
go get -u -v github.com/tpng/gopkgs
go get -u -v github.com/newhook/go-symbols
go get -u -v golang.org/x/tools/cmd/guru
go get -u -v github.com/cweill/gotests/...
```
vscode还有一项很强大的功能就是断点调试,结合[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试
```Go
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
brew install go-delve/delve/delve(mac可选)
```
如果有问题再来一遍:
```Go
go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv
```
注意:修改"dlv-cert"证书, 选择"显示简介"->"信任"->"代码签名" 修改为: 始终信任
打开首选项-工作区设置,配置launch.json:
```Go
{
"version": "0.2.0",
"configurations": [
{
"name": "main.go",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}",//工作空间路径
"env": {},
"args": [],
"showLog": true
}
]
}
```
## Atom
Atom是Github基于Electron和web技术构建的开源编辑器, 是一款很漂亮强大的编辑器缺点是速度比较慢。
首先要先安装下Atom下载地址: https://atom.io/
然后安装go-plus插件:
go-plus是Atom上面的一款开源的go语言开发环境的的插件
它需要依赖下面的go语言工具:
```Go
1.autocomplete-go gocode的代码自动提示
2.gofmt 使用goftm,goimports,goturns
3.builder-go:go-install 和go-test,验证代码,给出建议
4.gometalinet-linter:goline,vet,gotype的检查
5.navigator-godef:godef
6.tester-goo :go test
7.gorename :rename
```
在Atom中的 Preference 中可以找到install菜单,输入 go-plus,然后点击安装(install)
就会开始安装 go-plus go-plus 插件会自动安装对应的依赖插件如果没有安装对应的go的类库会自动运行: go get 安装。
##Goglang
Gogland是JetBrains公司推出的Go语言集成开发环境是Idea Go插件是强化版。Gogland同样基于IntelliJ平台开发支持JetBrains的插件体系。
目前正式版暂未发布。
下载地址:https://www.jetbrains.com/go/
## Vim
Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。

View File

@@ -1,6 +1,6 @@
# 1.5 总结
这一章中我们主要介绍了如何安装GoGo可以通过三种方式安装源码安装、标准包安装、第三方工具安装安装之后我们需要配置我们的开发环境然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目接着介绍了如何来进行项目编译、应用安装等问题这些需要用到很多Go命令所以接着就介绍了一些Go的常用命令工具包括编译、安装、格式化、测试等命令最后介绍了Go的开发工具目前有很多Go的开发工具LiteIDE、sublime、VIM、Emacs、Eclipse、Idea等工具读者可以根据自己熟悉的工具进行配置希望能够通过方便的工具快速的开发Go应用。
这一章中我们主要介绍了如何安装GoGo可以通过三种方式安装源码安装、标准包安装、第三方工具安装安装之后我们需要配置我们的开发环境然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目接着介绍了如何来进行项目编译、应用安装等问题这些需要用到很多Go命令所以接着就介绍了一些Go的常用命令工具包括编译、安装、格式化、测试等命令最后介绍了Go的开发工具目前有很多Go的开发工具LiteIDE、Sublime、VSCode、Atom、Goglang、VIM、Emacs、Eclipse、Idea等工具读者可以根据自己熟悉的工具进行配置希望能够通过方便的工具快速的开发Go应用。
## links
* [目录](<preface.md>)

View File

@@ -3,6 +3,7 @@
前面小节已经介绍了Web是基于http协议的一个服务Go语言里面提供了一个完善的net/http包通过http包可以很方便的就搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由静态文件模版cookie等数据进行设置和操作。
## http包建立Web服务器
```Go
package main
@@ -33,7 +34,8 @@
log.Fatal("ListenAndServe: ", err)
}
}
```
上面这个代码我们build之后然后执行web.exe,这个时候其实已经在9090端口监听http链接请求了。
在浏览器输入`http://localhost:9090`

View File

@@ -36,6 +36,7 @@ Handler处理请求和生成返回信息的处理逻辑
前面小节的代码里面我们可以看到Go是通过一个函数`ListenAndServe`来处理这些事情的这个底层其实这样处理的初始化一个server对象然后调用了`net.Listen("tcp", addr)`也就是底层用TCP协议搭建了一个服务然后监控我们设置的端口。
下面代码来自Go的http包的源码通过下面的代码我们可以看到整个的http处理过程
```Go
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
@@ -67,6 +68,7 @@ Handler处理请求和生成返回信息的处理逻辑
}
}
```
监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`首先通过Listener接收请求其次创建一个Conn最后单独开了一个goroutine把这个请求的数据当做参数扔给这个conn去服务`go c.serve()`。这个就是高并发体现了用户的每一次请求都是在一个新的goroutine去服务相互不影响。
那么如何具体分配到相应的函数来处理请求呢conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数我们前面例子传递的是nil也就是为空那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢这个变量就是一个路由器它用来匹配url跳转到其相应的handle函数那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则当请求uri为"/"路由就会转到函数sayhelloNameDefaultServeMux会调用ServeHTTP方法这个方法内部其实就是调用sayhelloName本身最后通过写入response的信息反馈到客户端。

View File

@@ -7,6 +7,7 @@ Go的http有两个核心功能Conn、ServeMux
与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立相互不会阻塞可以高效的响应网络事件。这是Go高效的保证。
Go在等待客户端请求里面是这样写的
```Go
c, err := srv.newConn(rw)
if err != nil {
@@ -14,12 +15,14 @@ Go在等待客户端请求里面是这样写的
}
go c.serve()
```
这里我们可以看到客户端的每次请求都会创建一个Conn这个Conn里面保存了该次请求的信息然后再传递到对应的handler该handler中便可以读取到相应的header信息这样保证了每个请求的独立性。
## ServeMux的自定义
我们前面小节讲述conn.server的时候其实内部是调用了http包默认的路由器通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢
它的结构如下:
```Go
type ServeMux struct {
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
@@ -27,7 +30,9 @@ Go在等待客户端请求里面是这样写的
hosts bool // 是否在任意的规则中带有host信息
}
```
下面看一下muxEntry
```Go
type muxEntry struct {
explicit bool // 是否精确匹配
@@ -35,13 +40,17 @@ Go在等待客户端请求里面是这样写的
pattern string //匹配字符串
}
```
接着看一下Handler的定义
```Go
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}
```
Handler是一个接口但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口为什么能添加呢原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果这个类型默认就实现了ServeHTTP这个接口即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型这样f就拥有了ServeHTTP方法。
```Go
type HandlerFunc func(ResponseWriter, *Request)
@@ -49,8 +58,9 @@ Handler是一个接口但是前一小节中的`sayhelloName`函数并没有
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
```
路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`
```Go
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
@@ -61,10 +71,11 @@ Handler是一个接口但是前一小节中的`sayhelloName`函数并没有
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
```
如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler然后执行`h.ServeHTTP(w, r)`
也就是调用对应路由的handler的ServerHTTP接口那么mux.Handler(r)怎么处理的呢?
```Go
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
@@ -92,12 +103,13 @@ Handler是一个接口但是前一小节中的`sayhelloName`函数并没有
}
return
}
```
原来他是根据用户请求的URL和路由器里面存储的map去匹配的当匹配到之后返回存储的handler调用这个handler的ServeHTTP接口就可以执行到相应的函数了。
通过上面这个介绍我们了解了整个路由过程Go其实支持外部实现的路由器 `ListenAndServe`的第二个参数就是用以配置外部路由器的它是一个Handler接口即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。
如下代码所示,我们自己实现了一个简易的路由器
```Go
package main
@@ -126,7 +138,7 @@ Handler是一个接口但是前一小节中的`sayhelloName`函数并没有
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
```
## Go代码的执行流程
通过对http包的分析之后现在让我们来梳理一下整个的代码执行过程。

View File

@@ -1,6 +1,7 @@
# 4.1 处理表单的输入
先来看一个表单递交的例子我们有如下的表单内容命名成文件login.gtpl(放入当前新建项目的目录里面)
```html
<html>
<head>
@@ -14,11 +15,11 @@
</form>
</body>
</html>
```
上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,会跳转到服务器的路由`login`里面我们首先要判断这个是什么方式传递过来POST还是GET呢
http包里面有一个很简单的方式就可以获取我们在前面web的例子的基础上来看看怎么处理login页面的form数据
```Go
package main
@@ -65,7 +66,7 @@ http包里面有一个很简单的方式就可以获取我们在前面web的
}
}
```
通过上面的代码我们可以看出获取请求方法是通过`r.Method`来完成的这是个字符串类型的变量返回GET, POST, PUT等method信息。
login函数中我们根据`r.Method`来判断是显示登录界面还是处理登录逻辑。当GET方式请求时显示登录界面其他方式请求时则处理登录逻辑如查询数据库、验证登录信息等。
@@ -89,6 +90,7 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理
图4.2 服务器端打印接受到的信息
`request.Form`是一个url.Values类型里面存储的是对应的类似`key=value`的信息下面展示了可以对form数据进行的一些操作:
```Go
v := url.Values{}
v.Set("name", "Ava")
@@ -99,7 +101,8 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理
fmt.Println(v.Get("name"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])
```
>**Tips**:
Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm所以不必提前调用。r.FormValue只会返回同名参数中的第一个若参数不存在则返回空字符串。

View File

@@ -6,17 +6,19 @@
## 必填字段
你想要确保从一个表单元素中得到一个值例如前面小节里面的用户名我们如何处理呢Go有一个内置函数`len`可以获取字符串的长度这样我们就可以通过len来获取数据的长度例如
```Go
if len(r.Form["username"][0])==0{
//为空的处理
}
```
`r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮则根本不会在r.Form中产生相应条目如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值如果是map的值必须通过上面的方式来获取。
## 数字
你想要确保一个表单输入框中获取的只能是数字例如你想通过表单获取某个人的具体年龄是50岁还是10岁而不是像“一把年纪了”或“年轻着呢”这种描述
如果我们是判断正整数那么我们先转化成int类型然后进行处理
```Go
getint,err:=strconv.Atoi(r.Form.Get("age"))
if err!=nil{
@@ -27,36 +29,40 @@
if getint >100 {
//太大了
}
```
还有一种方式就是正则匹配的方式
```Go
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
return false
}
```
对于性能要求很高的用户来说这是一个老生常谈的问题了他们认为应该尽量避免使用正则表达式因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉而且你在其它语言中也在使用它那么在Go里面使用正则表达式将是一个便利的方式。
>Go实现的正则是[RE2](http://code.google.com/p/re2/wiki/Syntax)所有的字符都是UTF-8编码的。
## 中文
有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有两种方式来验证,可以使用 `unicode` 包提供的 `func Is(rangeTab *RangeTable, r rune) bool` 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示
```Go
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
return false
}
```
## 英文
我们期望通过表单元素获取一个英文值例如我们想知道一个用户的英文名应该是astaxie而不是asta谢。
我们可以很简单的通过正则验证数据:
```Go
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
return false
}
```
## 电子邮件地址
你想知道用户输入的一个Email地址是否正确通过如下这个方式可以验证
```Go
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
fmt.Println("no")
@@ -64,26 +70,29 @@
fmt.Println("yes")
}
```
## 手机号码
你想要判断用户输入的手机号码是否正确,通过正则也可以验证:
```Go
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
return false
}
```
## 下拉菜单
如果我们想要判断表单里面`<select>`元素生成的下拉菜单中是否有被选中的项目。有些时候黑客可能会伪造这个下拉菜单不存在的值发送给你,那么如何判断这个值是否是我们预设的值呢?
我们的select可能是这样的一些元素
```html
<select name="fruit">
<option value="apple">apple</option>
<option value="pear">pear</option>
<option value="banane">banane</option>
</select>
```
那么我们可以这样来验证
```Go
slice:=[]string{"apple","pear","banane"}
@@ -95,14 +104,16 @@
}
return false
```
## 单选按钮
如果我们想要判断radio按钮是否有一个被选中了我们页面的输出可能就是一个男、女性别的选择但是也可能一个15岁大的无聊小孩一手拿着http协议的书另一只手通过telnet客户端向你的程序在发送请求呢你设定的性别男值是1女是2他给你发送一个3你的程序会出现异常吗因此我们也需要像下拉菜单的判断方式类似判断我们获取的值是我们预设的值而不是额外的值。
```html
<input type="radio" name="gender" value="1">
<input type="radio" name="gender" value="2">
```
那我们也可以类似下拉菜单的做法一样
```Go
slice:=[]int{1,2}
@@ -112,15 +123,17 @@
}
}
return false
```
## 复选框
有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。
```html
<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">篮球
<input type="checkbox" name="interest" value="tennis">网球
```
对于复选框我们的验证和单选有点不一样因为接收到的数据是一个slice
```Go
slice:=[]string{"football","basketball","tennis"}
a:=Slice_diff(r.Form["interest"],slice)
@@ -129,7 +142,7 @@
}
return false
```
上面这个函数`Slice_diff`包含在我开源的一个库里面(操作slice和map的库)[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
## 日期和时间
@@ -137,14 +150,16 @@
用户在日程表中安排8月份的第45天开会或者提供未来的某个时间作为生日。
Go里面提供了一个time的处理包我们可以把用户的输入年月日转化成相应的时间然后进行逻辑判断
```Go
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位我们两个都需要验证
```Go
//验证15位身份证15位的是全部数字
if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
@@ -155,7 +170,8 @@ Go里面提供了一个time的处理包我们可以把用户的输入年月
if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
return false
}
```
上面列出了我们一些常用的服务器端的表单元素验证希望通过这个引导入门能够让你对Go的数据验证有所了解特别是Go里面的正则处理。
## links

View File

@@ -14,11 +14,12 @@
我们看4.1小节的例子
```Go
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
```
如果我们输入的username是`<script>alert()</script>`,那么我们可以在浏览器上面看到输出如下所示:
![](images/4.3.escape.png?raw=true)
@@ -26,23 +27,25 @@
图4.3 Javascript过滤之后的输出
Go的html/template包默认帮你过滤了html标签但是有时候你只想要输出这个`<script>alert()</script>`看起来正常的信息该怎么处理请使用text/template。请看下面的例子
```Go
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
```
输出
Hello, <script>alert('you have been pwned')</script>!
或者使用template.HTML类型
```Go
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
```
输出
Hello, <script>alert('you have been pwned')</script>!
@@ -50,12 +53,13 @@ Go的html/template包默认帮你过滤了html标签但是有时候你只想
转换成`template.HTML`后,变量的内容也不会被转义
转义的例子:
```Go
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
```
转义之后的输出:
Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

View File

@@ -5,6 +5,7 @@
解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时先检查带有该惟一值的表单是否已经递交过了。如果是拒绝再次递交如果不是则处理表单进行逻辑处理。另外如果是采用了Ajax模式递交表单的话当表单递交后通过javascript来禁用表单的递交按钮。
我继续拿4.2小节的例子优化:
```html
<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">篮球
@@ -13,8 +14,9 @@
密码:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陆">
```
我们在模版里面增加了一个隐藏字段`token`这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到服务器端(session来控制我们将在第六章讲解如何保存),以方便表单提交时比对判定。
```Go
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) //获取请求的方法
@@ -41,7 +43,7 @@
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
}
}
```
上面的代码输出到页面的源码如下:
![](images/4.4.token.png?raw=true)

View File

@@ -2,12 +2,14 @@
你想处理一个由用户上传的文件比如你正在建设一个类似Instagram的网站你需要存储用户拍摄的照片。这种需求该如何实现呢
要使表单能够上传文件首先第一步就是要添加form的`enctype`属性,`enctype`属性有如下三种情况:
```
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
```
所以创建新的表单html文件, 命名为upload.gtpl, html代码应该类似于:
```html
<html>
<head>
@@ -21,8 +23,9 @@
</form>
</body>
</html>
```
在服务器端我们增加一个handlerFunc:
```Go
http.HandleFunc("/upload", upload)
@@ -55,7 +58,7 @@
io.Copy(f, file)
}
}
```
通过上面的代码可以看到,处理文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示`maxMemory`,调用`ParseMultipartForm`之后,上传的文件存储在`maxMemory`大小的内存里面,如果文件大小超过了`maxMemory`,那么剩下的部分将存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。
>获取其他非文件字段信息的时候就不需要调用`r.ParseForm`因为在需要的时候Go自动会去调用。而且`ParseMultipartForm`调用一次之后,后面再次调用不会再有效果。
@@ -67,13 +70,14 @@
3. 使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
文件handler是multipart.FileHeader,里面存储了如下结构信息
```Go
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// contains filtered or unexported fields
}
```
我们通过上面的实例代码打印出来上传文件的信息如下
![](images/4.5.upload2.png?raw=true)
@@ -83,6 +87,7 @@
## 客户端上传文件
我们上面的例子演示了如何通过表单上传文件然后在服务器端处理文件其实Go支持模拟客户端表单功能支持文件上传详细用法请看如下示例
```Go
package main
@@ -145,7 +150,7 @@
postFile(filename, target_url)
}
```
上面的例子详细展示了客户端如何向服务器上传一个文件的例子客户端通过multipart.Write把文件的文本流写入一个缓存中然后调用http的Post方法把缓存传到服务器。
>如果你还有其他普通字段例如username之类的需要同时写入那么可以调用multipart的WriteField方法写很多其他类似的字段。

View File

@@ -5,6 +5,7 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动而是为开发
这个存在于database/sql的函数是用来注册数据库驱动的当第三方开发者开发数据库驱动时都会实现init函数在init里面会调用这个`Register(name string, driver driver.Driver)`完成本驱动的注册。
我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的
```Go
//https://github.com/mattn/go-sqlite3驱动
func init() {
@@ -18,13 +19,14 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动而是为开发
Register("SET NAMES utf8")
sql.Register("mymysql", &d)
}
```
我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。
```Go
var drivers = make(map[string]driver.Driver)
drivers[name] = driver
```
因此通过database/sql的注册函数可以同时注册多个数据库驱动只要不重复。
>在我们使用database/sql接口和第三方库的时候经常看到如下:
@@ -40,31 +42,34 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动而是为开发
## driver.Driver
Driver是一个数据库驱动的接口他定义了一个method Open(name string)这个方法返回一个数据库的Conn接口。
```Go
type Driver interface {
Open(name string) (Conn, error)
}
```
返回的Conn只能用来进行一次goroutine的操作也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误
```Go
...
go goroutineA (Conn) //执行查询操作
go goroutineB (Conn) //执行插入操作
...
```
上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。
第三方驱动都会定义这个函数它会解析name参数来获取相关数据库的连接信息解析完成后它将使用此信息来初始化一个Conn并返回它。
## driver.Conn
Conn是一个数据库连接的接口定义他定义了一系列方法这个Conn只能应用在一个goroutine里面不能使用在多个goroutine里面详情请参考上面的说明。
```Go
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
```
Prepare函数返回与当前连接相关的执行Sql语句的准备状态可以进行查询、删除等操作。
Close函数关闭当前的连接执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool所以你不用再去实现缓存conn之类的这样会容易引起问题。
@@ -73,6 +78,7 @@ Begin函数返回一个代表事务处理的Tx通过它你可以进行查询,
## driver.Stmt
Stmt是一种准备好的状态和Conn相关联而且只能应用于一个goroutine中不能应用于多个goroutine。
```Go
type Stmt interface {
Close() error
@@ -80,7 +86,7 @@ Stmt是一种准备好的状态和Conn相关联而且只能应用于一个
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
```
Close函数关闭当前的链接状态但是如果当前正在执行queryquery还是有效返回rows数据。
NumInput函数返回当前预留参数的个数当返回>=0时数据库驱动就会智能检查调用者的参数。当数据库驱动包不知道预留参数的时候返回-1。
@@ -92,44 +98,48 @@ Query函数执行Prepare准备好的sql传入需要的参数执行select操
## driver.Tx
事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以
```Go
type Tx interface {
Commit() error
Rollback() error
}
```
这两个函数一个用来递交一个事务,一个用来回滚事务。
## driver.Execer
这是一个Conn可选择实现的接口
```Go
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
```
如果这个接口没有定义那么在调用DB.Exec,就会首先调用Prepare返回Stmt然后执行Stmt的Exec然后关闭Stmt。
## driver.Result
这个是执行Update/Insert等操作返回的结果接口定义
```Go
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
```
LastInsertId函数返回由数据库执行插入操作得到的自增ID号。
RowsAffected函数返回query操作影响的数据条目数。
## driver.Rows
Rows是执行查询返回的结果集接口定义
```Go
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
```
Columns函数返回查询数据库表的字段信息这个返回的slice和sql查询的字段一一对应而不是返回整个表的所有字段。
Close函数用来关闭Rows迭代器。
@@ -139,19 +149,22 @@ Next函数用来返回下一条数据把数据赋值给dest。dest里面的
## driver.RowsAffected
RowsAffected其实就是一个int64的别名但是他实现了Result接口用来底层实现Result的表示方式
```Go
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
```
## driver.Value
Value其实就是一个空接口他可以容纳任何的数据
```Go
type Value interface{}
```
drive的Value是驱动必须能够操作的ValueValue要么是nil要么是下面的任意一种
```Go
int64
float64
@@ -159,14 +172,15 @@ drive的Value是驱动必须能够操作的ValueValue要么是nil要么是
[]byte
string [*]除了Rows.Next返回的不能是string.
time.Time
```
## driver.ValueConverter
ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口
```Go
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
```
在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到这个ValueConverter有很多好处
- 转化driver.value到数据库表相应的字段例如int64的数据如何转化成数据库表uint16字段
@@ -175,17 +189,19 @@ ValueConverter接口定义了如何把一个普通的值转化成driver.Value的
## driver.Valuer
Valuer接口定义了返回一个driver.Value的方式
```Go
type Valuer interface {
Value() (Value, error)
}
```
很多类型都实现了这个Value方法用来自身与driver.Value的转化。
通过上面的讲解,你应该对于驱动的开发有了一个基本的了解,一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了,在此不再赘述。
## database/sql
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法用以简化数据库操作,同时内部还建议性地实现一个conn pool。
```Go
type DB struct {
driver driver.Driver
@@ -194,7 +210,7 @@ database/sql在database/sql/driver提供的接口基础上定义了一些更高
freeConn []driver.Conn
closed bool
}
```
我们可以看到Open函数返回的是DB对象里面有一个freeConn它就是那个简易的连接池。它的实现相当简单或者说简陋就是当执行Db.prepare的时候会`defer db.putConn(ci, err)`,也就是把这个连接放入连接池每次调用conn的时候会先判断freeConn的长度是否大于0大于0说明有可以复用的conn直接拿出来用就是了如果不大于0则创建一个conn,然后再返回之。

View File

@@ -16,6 +16,7 @@ Go中支持MySQL的驱动目前比较多有如下几种有些是支持data
## 示例代码
接下来的几个小节里面我们都将采用同一个数据库表结构数据库test用户表userinfo关联用户信息表userdetail。
```sql
CREATE TABLE `userinfo` (
`uid` INT(10) NOT NULL AUTO_INCREMENT,
@@ -31,7 +32,7 @@ Go中支持MySQL的驱动目前比较多有如下几种有些是支持data
`profile` TEXT NULL,
PRIMARY KEY (`uid`)
)
```
如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作
```Go

View File

@@ -13,6 +13,7 @@ Go支持sqlite的驱动也比较多但是好多都是不支持database/sql接
## 实例代码
示例的数据库表结构如下所示相应的建表SQL
```sql
CREATE TABLE `userinfo` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -27,7 +28,7 @@ Go支持sqlite的驱动也比较多但是好多都是不支持database/sql接
`profile` TEXT NULL,
PRIMARY KEY (`uid`)
);
```
看下面Go程序是如何操作数据库表数据:增删改查
```Go

View File

@@ -17,6 +17,7 @@ Go实现的支持PostgreSQL的驱动也很多因为国外很多人在开发
## 实例代码
数据库建表语句:
```sql
CREATE TABLE userinfo
(
@@ -36,6 +37,7 @@ Go实现的支持PostgreSQL的驱动也很多因为国外很多人在开发
)
WITH(OIDS=FALSE);
```
看下面这个Go如何操作数据库表数据:增删改查
```Go

View File

@@ -1,58 +1,86 @@
# 5.5 使用beedb库进行ORM开发
beedb是我开发的一个Go进行ORM操作的库它采用了Go style方式对数据库进行操作实现了struct到数据表记录的映射。beedb是一个十分轻量级的Go ORM框架开发这个库的本意降低复杂的ORM学习曲线尽可能在ORM的运行效率和功能之间寻求一个平衡beedb是目前开源的Go ORM框架中实现比较完整的一个库而且运行效率相当不错功能也基本能满足需求。但是目前还不支持关系关联这个是接下来版本升级的重点。
# 5.5 使用beedb/beego orm(暂未完成,请参考beego.me文档)库进行ORM开发
beedb/beego orm是我开发的一个Go进行ORM操作的库它采用了Go style方式对数据库进行操作实现了struct到数据表记录的映射。beedb是一个十分轻量级的Go ORM框架开发这个库的本意降低复杂的ORM学习曲线尽可能在ORM的运行效率和功能之间寻求一个平衡beedb是目前开源的Go ORM框架中实现比较完整的一个库而且运行效率相当不错功能也基本能满足需求。但是目前还不支持关系关联这个是接下来版本升级的重点。
beedb是支持database/sql标准接口的ORM库所以理论上来说只要数据库驱动支持database/sql接口就可以无缝的接入beedb。目前我测试过的驱动包括下面几个
beedb/beego orm是支持database/sql标准接口的ORM库所以理论上来说只要数据库驱动支持database/sql接口就可以无缝的接入beedb。目前我测试过的驱动包括下面几个
Mysql:github.com/ziutek/mymysql/godrv[*]
Mysql:code.google.com/p/go-mysql-driver[*]
Mysql: [github/go-mysql-driver/mysql](github.com/go-sql-driver/mysql)
PostgreSQL:github.com/bmizerany/pq[*]
PostgreSQL: [github.com/bmizerany/pq](github.com/lib/pq)
SQLite:github.com/mattn/go-sqlite3[*]
SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3)
MS ADODB: github.com/mattn/go-adodb[*]
Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv)
MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb)
ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc)
ODBC: bitbucket.org/miquella/mgodbc[*]
## 安装
beego orm支持go get方式安装是完全按照Go Style的方式来实现的。
go get github.com/astaxie/beego
beedb支持go get方式安装是完全按照Go Style的方式来实现的。
go get github.com/astaxie/beedb
go get github.com/astaxie/beedb
## 如何初始化
首先你需要import相应的数据库驱动包、database/sql标准接口包以及beedb包,如下所示:
首先你需要import相应的数据库驱动包、database/sql标准接口包以及beego orm包,如下所示:
```Go
import (
"database/sql"
"github.com/astaxie/beedb"
_ "github.com/ziutek/mymysql/godrv"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
)
```
导入必须的package之后,我们需要打开到数据库的链接然后创建一个beego orm对象以MySQL为例),如下所示
beego orm:
导入必须的package之后,我们需要打开到数据库的链接然后创建一个beedb对象以MySQL为例),如下所示
```Go
func init() {
// set default database
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
}
func main() {
orm := orm.NewOrm()
}
```
beedb:
```Go
db, err := sql.Open("mymysql", "test/xiemengjun/123456")
if err != nil {
panic(err)
}
orm := beedb.New(db)
```
beedb的New函数实际上应该有两个参数第一个参数标准接口的db第二个参数是使用的数据库引擎如果你使用的数据库引擎是MySQL/Sqlite,那么第二个参数都可以省略。
如果你使用的数据库是SQLServer那么初始化需要
```Go
orm = beedb.New(db, "mssql")
```
如果你使用了PostgreSQL那么初始化需要
```Go
orm = beedb.New(db, "pg")
```
目前beedb支持打印调试你可以通过如下的代码实现调试
```Go
beedb.OnDebug=true
```
接下来我们的例子采用前面的数据库表Userinfo现在我们建立相应的struct
```Go
type Userinfo struct {
Uid int `PK` //如果表的主键不是id那么需要加上pk注释显式的说这个字段是主键
@@ -61,28 +89,32 @@ beedb的New函数实际上应该有两个参数第一个参数标准接口的
Created time.Time
}
>注意一点beedb针对驼峰命名会自动帮你转化成下划线字段例如你定义了Struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。
```
>注意一点beego orm针对驼峰命名会自动帮你转化成下划线字段例如你定义了Struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。
## 插入数据
下面的代码演示了如何插入一条记录可以看到我们操作的是struct对象而不是原生的sql语句最后通过调用Save接口将数据保存到数据库。
```Go
var saveone Userinfo
saveone.Username = "Test Add User"
saveone.Departname = "Test Add Departname"
saveone.Created = time.Now()
orm.Save(&saveone)
```
我们看到插入之后`saveone.Uid`就是插入成功之后的自增ID。Save接口会自动帮你存进去。
beedb接口提供了另外一种插入的方式map数据插入。
```Go
add := make(map[string]interface{})
add["username"] = "astaxie"
add["departname"] = "cloud develop"
add["created"] = "2012-12-02"
orm.SetTable("userinfo").Insert(add)
```
插入多条数据
```Go
addslice := make([]map[string]interface{}, 0)
add:=make(map[string]interface{})
@@ -95,25 +127,27 @@ beedb接口提供了另外一种插入的方式map数据插入。
add2["created"] = "2012-12-02"
addslice =append(addslice, add, add2)
orm.SetTable("userinfo").InsertBatch(addslice)
```
上面的操作方式有点类似链式查询熟悉jquery的同学应该会觉得很亲切每次调用的method都会返回原orm对象以便可以继续调用该对象上的其他method。
上面我们调用的SetTable函数是显式的告诉ORM我要执行的这个map对应的数据库表是`userinfo`
## 更新数据
继续上面的例子来演示更新操作现在saveone的主键已经有值了此时调用save接口beedb内部会自动调用update以进行数据的更新而非插入操作。
```Go
saveone.Username = "Update Username"
saveone.Departname = "Update Departname"
saveone.Created = time.Now()
orm.Save(&saveone) //现在saveone有了主键值就执行更新操作
```
更新数据也支持直接使用map操作
```Go
t := make(map[string]interface{})
t["username"] = "astaxie"
orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t)
```
这里我们调用了几个beedb的函数
SetPK显式的告诉ORM数据库表`userinfo`的主键是`uid`
@@ -125,46 +159,55 @@ Updata函数接收map类型的数据执行更新数据。
beedb的查询接口比较灵活具体使用请看下面的例子
例子1根据主键获取数据
```Go
var user Userinfo
//Where接受两个参数支持整形参数
orm.Where("uid=?", 27).Find(&user)
```
例子2
```Go
var user2 Userinfo
orm.Where(3).Find(&user2) // 这是上面版本的缩写版,可以省略主键
```
例子3不是主键类型的的条件
```Go
var user3 Userinfo
//Where接受两个参数支持字符型的参数
orm.Where("name = ?", "john").Find(&user3)
```
例子4更加复杂的条件
```Go
var user4 Userinfo
//Where支持三个参数
orm.Where("name = ? and age < ?", "john", 88).Find(&user4)
```
可以通过如下接口获取多条数据,请看示例
例子1根据条件id>3获取20位置开始的10条数据的数据
```Go
var allusers []Userinfo
err := orm.Where("id > ?", "3").Limit(10,20).FindAll(&allusers)
```
例子2省略limit第二个参数默认从0开始获取10条数据
```Go
var tenusers []Userinfo
err := orm.Where("id > ?", "3").Limit(10).FindAll(&tenusers)
```
例子3获取全部数据
```Go
var everyone []Userinfo
err := orm.OrderBy("uid desc,username asc").FindAll(&everyone)
```
上面这些里面里面我们看到一个函数Limit他是用来控制查询结构条数的。
Limit:支持两个参数第一个参数表示查询的条数第二个参数表示读取数据的起始位置默认为0。
@@ -172,9 +215,10 @@ Limit:支持两个参数,第一个参数表示查询的条数,第二个参
OrderBy:这个函数用来进行查询排序,参数是需要排序的条件。
上面这些例子都是将获取的的数据直接映射成struct对象如果我们只是想获取一些数据到map以下方式可以实现
```Go
a, _ := orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap()
```
上面和这个例子里面又出现了一个新的接口函数Select这个函数用来指定需要查询多少个字段。默认为全部字段`*`
FindMap()函数返回的是`[]map[string][]byte`类型,所以你需要自己作类型转换。
@@ -183,25 +227,29 @@ FindMap()函数返回的是`[]map[string][]byte`类型,所以你需要自己
beedb提供了丰富的删除数据接口请看下面的例子
例子1删除单条数据
```Go
//saveone就是上面示例中的那个saveone
orm.Delete(&saveone)
```
例子2删除多条数据
```Go
//alluser就是上面定义的获取多条数据的slice
orm.DeleteAll(&alluser)
```
例子3根据sql删除数据
```Go
orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow()
```
## 关联查询
目前beedb还不支持struct的关联关系但是有些应用却需要用到连接查询所以现在beedb提供了一个简陋的实现方案
```Go
a, _ := orm.SetTable("userinfo").Join("LEFT", "userdeatail", "userinfo.uid=userdeatail.uid").Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdeatail.profile").FindMap()
```
上面代码中我们看到了一个新的接口Join函数这个函数带有三个参数
- 第一个参数可以是INNER, LEFT, OUTER, CROSS等
@@ -211,9 +259,10 @@ beedb提供了丰富的删除数据接口请看下面的例子
## Group By和Having
针对有些应用需要用到group by和having的功能beedb也提供了一个简陋的实现
```Go
a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap()
```
上面的代码中出现了两个新接口函数
GroupBy:用来指定进行groupby的字段
@@ -225,7 +274,7 @@ Having:用来指定having执行的时候的条件
- 实现interface设计类似databse/sql/driver的设计设计beedb的接口然后去实现相应数据库的CRUD操作
- 实现关联数据库设计,支持一对一,一对多,多对多的实现,示例代码如下:
```Go
type Profile struct{
Nickname string
@@ -239,7 +288,7 @@ Having:用来指定having执行的时候的条件
Created time.Time
Profile `HasOne`
}
```
- 自动建库建表建索引
- 实现连接池的实现采用goroutine

View File

@@ -9,6 +9,7 @@ redis是一个key-value存储系统。和Memcached类似它支持存储的val
目前应用redis最广泛的应该是新浪微博平台其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的[互联网企业](http://redis.io/topics/whos-using-redis)
Go目前支持redis的驱动有如下
- https://github.com/garyburd/redigo (推荐)
- https://github.com/alphazero/Go-Redis
- http://code.google.com/p/tideland-rdc/
- https://github.com/simonz05/godis
@@ -19,6 +20,7 @@ Go目前支持redis的驱动有如下
https://github.com/astaxie/goredis
接下来的以我自己fork的这个redis驱动为例来演示如何进行数据的操作
```Go
package main
@@ -50,6 +52,7 @@ https://github.com/astaxie/goredis
client.Del("l")
}
```
我们可以看到操作redis非常的方便而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。
## mongoDB
@@ -65,6 +68,7 @@ MongoDB是一个高性能开源无模式的文档型数据库是一个
目前Go支持mongoDB最好的驱动就是[mgo](http://labix.org/mgo)这个驱动目前最有可能成为官方的pkg。
下面我将演示如何通过Go来操作mongoDB
```Go
package main
@@ -104,6 +108,7 @@ MongoDB是一个高性能开源无模式的文档型数据库是一个
fmt.Println("Phone:", result.Phone)
}
```
我们可以看出来mgo的操作方式和beedb的操作方式几乎类似都是基于struct的操作方式这个就是Go Style。

View File

@@ -35,10 +35,12 @@ cookie是有时间限制的根据生命期不同分成两种会话cookie
### Go设置cookie
Go语言中通过net/http包中的SetCookie来设置
```Go
http.SetCookie(w ResponseWriter, cookie *Cookie)
```
w表示需要写入的responsecookie是一个struct让我们来看一下cookie对象是怎么样的
```Go
type Cookie struct {
Name string
@@ -58,26 +60,30 @@ w表示需要写入的responsecookie是一个struct让我们来看一下co
Unparsed []string // Raw text of unparsed attribute-value pairs
}
```
我们来看一个例子如何设置cookie
```Go
expiration := time.Now()
expiration = expiration.AddDate(1, 0, 0)
cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration}
http.SetCookie(w, &cookie)
```
  
### Go读取cookie
上面的例子演示了如何设置cookie数据我们这里来演示一下如何读取cookie
```Go
cookie, _ := r.Cookie("username")
fmt.Fprint(w, cookie)
```
还有另外一种读取方式
```Go
for _, cookie := range r.Cookies() {
fmt.Fprint(w, cookie.Name)
}
```
可以看到通过request获取cookie非常方便。
## session

View File

@@ -32,6 +32,7 @@ session的基本原理是由服务器为每个会话维护一份信息数据
### Session管理器
定义一个全局的session管理器
```Go
type Manager struct {
cookieName string //private cookiename
@@ -48,15 +49,18 @@ session的基本原理是由服务器为每个会话维护一份信息数据
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
}
```
Go实现整个的流程应该也是这样的在main包中创建一个全局的session管理器
```Go
var globalSessions *session.Manager
//然后在init函数中初始化
func init() {
globalSessions, _ = NewManager("memory","gosessionid",3600)
}
```
我们知道session是保存在服务器端的数据它可以以任何的方式存储比如存储在内存、数据库或者文件中。因此我们抽象出一个Provider接口用以表征session管理器底层存储结构。
```Go
type Provider interface {
SessionInit(sid string) (Session, error)
@@ -64,13 +68,14 @@ Go实现整个的流程应该也是这样的在main包中创建一个全局
SessionDestroy(sid string) error
SessionGC(maxLifeTime int64)
}
```
- SessionInit函数实现Session的初始化操作成功则返回此新的Session变量
- SessionRead函数返回sid所代表的Session变量如果不存在那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量
- SessionDestroy函数用来销毁sid对应的Session变量
- SessionGC根据maxLifeTime来删除过期的数据
那么Session接口需要实现什么样的功能呢有过Web开发经验的读者知道对Session的处理基本就 设置值、读取值、删除值以及获取当前sessionID这四个操作所以我们的Session接口也就实现这四个操作。
```Go
type Session interface {
Set(key, value interface{}) error //set session value
@@ -78,9 +83,11 @@ Go实现整个的流程应该也是这样的在main包中创建一个全局
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
}
```
>以上设计思路来源于database/sql/driver先定义好接口然后具体的存储session的结构实现相应的接口并注册后相应功能这样就可以使用了以下是用来随需注册存储session的结构的Register函数的实现。
```Go
var provides = make(map[string]Provider)
// Register makes a session provide available by the provided name.
@@ -95,11 +102,13 @@ Go实现整个的流程应该也是这样的在main包中创建一个全局
}
provides[name] = provider
}
```
### 全局唯一的Session ID
Session ID是用来识别访问Web应用的每一个用户因此必须保证它是全局唯一的GUID下面代码展示了如何满足这一需求
```Go
func (manager *Manager) sessionId() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
@@ -107,9 +116,10 @@ Session ID是用来识别访问Web应用的每一个用户因此必须保证
}
return base64.URLEncoding.EncodeToString(b)
}
```
### session创建
我们需要为每个来访用户分配或获取与他相关连的Session以便后面根据Session信息来验证操作。SessionStart这个函数就是用来检测是否已经有某个Session与当前来访用户发生了关联如果没有则创建之。
```Go
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
manager.lock.Lock()
@@ -126,8 +136,9 @@ Session ID是用来识别访问Web应用的每一个用户因此必须保证
}
return
}
```
我们用前面login操作来演示session的运用
```Go
func login(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
@@ -141,11 +152,12 @@ Session ID是用来识别访问Web应用的每一个用户因此必须保证
http.Redirect(w, r, "/", 302)
}
}
```
### 操作值:设置、读取和删除
SessionStart函数返回的是一个满足Session接口的变量那么我们该如何用他来对session数据进行操作呢
上面的例子中的代码`session.Get("uid")`已经展示了基本的读取数据的操作,现在我们再来看一下详细的操作:
```Go
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
@@ -166,13 +178,14 @@ SessionStart函数返回的是一个满足Session接口的变量那么我们
w.Header().Set("Content-Type", "text/html")
t.Execute(w, sess.Get("countnum"))
}
```
通过上面的例子可以看到Session的操作和操作key/value数据库类似:Set、Get、Delete等操作
因为Session有过期的概念所以我们定义了GC操作当访问过期时间满足GC的触发条件后将会引起GC但是当我们进行了任意一个session操作都会对Session实体进行更新都会触发对最后访问时间的修改这样当GC的时候就不会误删除还在使用的Session实体。
### session重置
我们知道Web应用中有用户退出这个操作那么当用户退出应用的时候我们需要对该用户的session数据进行销毁操作上面的代码已经演示了如何使用session重置操作下面这个函数就是实现了这个功能
```Go
//Destroy sessionid
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
@@ -189,9 +202,10 @@ SessionStart函数返回的是一个满足Session接口的变量那么我们
}
}
```
### session销毁
我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动
```Go
func init() {
go globalSessions.GC()
@@ -203,7 +217,7 @@ SessionStart函数返回的是一个满足Session接口的变量那么我们
manager.provider.SessionGC(manager.maxlifetime)
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
}
```
我们可以看到GC充分利用了time包中的定时器功能当超时`maxLifeTime`之后调用GC函数这样就可以保证`maxLifeTime`时间内的session都是可用的类似的方案也可以用于统计在线用户数之类的。
## 总结

View File

@@ -1,5 +1,6 @@
# 6.3 session存储
上一节我们介绍了Session管理器的实现原理定义了存储session的接口这小节我们将示例一个基于内存的session存储接口的实现其他的存储方式读者可以自行参考示例来实现内存的实现请看下面的例子代码
```Go
package memory
@@ -112,15 +113,17 @@
pder.sessions = make(map[string]*list.Element, 0)
session.Register("memory", pder)
}
```
上面这个代码实现了一个内存存储的session机制。通过init函数注册到session管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢请看下面的代码
```Go
import (
"github.com/astaxie/session"
_ "github.com/astaxie/session/providers/memory"
)
```
当import的时候已经执行了memory函数里面的init函数这样就已经注册到session管理器中我们就可以使用了通过如下方式就可以初始化一个session管理器
```Go
var globalSessions *session.Manager
@@ -130,7 +133,7 @@
go globalSessions.GC()
}
```
## links
* [目录](<preface.md>)
* 上一节: [Go如何使用session](<06.2.md>)

View File

@@ -4,6 +4,7 @@ session劫持是一种广泛存在的比较严重的安全威胁在session技
本节将通过一个实例来演示会话劫持希望通过这个实例能让读者更好地理解session的本质。
## session劫持过程
我们写了如下的代码来展示一个count计数器
```Go
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
@@ -18,11 +19,12 @@ session劫持是一种广泛存在的比较严重的安全威胁在session技
t.Execute(w, sess.Get("countnum"))
}
```
count.gtpl的代码如下所示
```Go
Hi. Now count:{{.}}
```
然后我们在浏览器里面刷新可以看到如下内容:
![](images/6.4.hijack.png?raw=true)
@@ -56,6 +58,7 @@ count.gtpl的代码如下所示
其中一个解决方案就是sessionID的值只允许cookie设置而不是通过URL重置方式设置同时设置cookie的httponly为true,这个属性是设置是否可通过客户端脚本访问这个设置的cookie第一这个可以防止这个cookie被XSS读取从而引起session劫持第二cookie设置不会像URL重置方式那么容易获取sessionID。
第二步就是在每个请求里面加上token实现类似前面章节里面讲的防止form重复递交类似的功能我们在每个请求里面加上一个隐藏的token然后每次验证这个token从而保证用户的请求都是唯一性。
```Go
h := md5.New()
salt:="astaxie%^7&8888"
@@ -66,9 +69,10 @@ count.gtpl的代码如下所示
}
sess.Set("token",token)
```
### 间隔生成新的SID
还有一个解决方案就是我们给session额外设置一个创建时间的值一旦过了一定的时间我们销毁这个sessionID重新生成新的session这样可以一定程度上防止session劫持的问题。
```Go
createtime := sess.Get("createtime")
if createtime == nil {
@@ -77,7 +81,7 @@ count.gtpl的代码如下所示
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
```
session启动后我们设置了一个值用于记录生成sessionID的时间。通过判断每次请求是否过期(这里设置了60秒)定期生成新的ID这样使得攻击者获取有效sessionID的机会大大降低。
上面两个手段的组合可以在实践中消除session劫持的风险一方面 由于sessionID频繁改变使攻击者难有机会获取有效的sessionID另一方面因为sessionID只能在cookie中传递然后设置了httponly所以基于URL攻击的可能性为零同时被XSS获取sessionID也不可能。最后由于我们还设置了MaxAge=0这样就相当于session cookie不会留在浏览器的历史记录里面。

View File

@@ -4,6 +4,7 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随
这个小节不会涉及XML规范相关的内容如需了解相关知识请参考其他文献而是介绍如何用Go语言来编解码XML文件相关的知识。
假如你是一名运维人员你为你所管理的所有服务器生成了如下内容的xml的配置文件
```xml
<?xml version="1.0" encoding="utf-8"?>
<servers version="1">
@@ -16,17 +17,19 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随
<serverIP>127.0.0.2</serverIP>
</server>
</servers>
```
上面的XML文档描述了两个服务器的信息包含了服务器名和服务器的IP信息接下来的Go例子以此XML描述的信息进行操作。
## 解析XML
如何解析如上这个XML文件呢 我们可以通过xml包的`Unmarshal`函数来达到我们的目的
```Go
func Unmarshal(data []byte, v interface{}) error
```
data接收的是XML数据流v是需要输出的结构定义为interface也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换因为struct和XML都有类似树结构的特征。
示例代码如下:
```Go
package main
@@ -72,8 +75,9 @@ data接收的是XML数据流v是需要输出的结构定义为interface
fmt.Println(v)
}
```
XML本质上是一种树形的数据格式而我们可以定义与之匹配的go 语言的 struct类型然后通过xml.Unmarshal来将xml中的数据解析成对应的struct对象。如上例子输出如下数据
```xml
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
<server>
@@ -86,11 +90,12 @@ XML本质上是一种树形的数据格式而我们可以定义与之匹配
</server>
}
```
上面的例子中将xml文件解析成对应的struct对象是通过`xml.Unmarshal`来完成的这个过程是如何实现的可以看到我们的struct定义后面多了一些类似于`xml:"serverName"`这样的内容,这个是struct的一个特性它们被称为 struct tag它们是用来辅助反射的。我们来看一下`Unmarshal`的定义:
```Go
func Unmarshal(data []byte, v interface{}) error
```
我们看到函数定义了两个参数第一个是XML数据流第二个是存储的对应类型目前支持struct、slice和stringXML包内部采用了反射来进行数据的映射所以v里面的字段必须是导出的。`Unmarshal`解析的时候XML元素和字段怎么对应起来的呢这是有一个优先级读取流程的首先会读取struct tag如果没有那么就会对应字段名。必须注意一点的是解析的时候tag、字段名、XML元素都是大小写敏感的所以必须一一对应字段。
Go语言的反射机制可以利用这些tag信息来将来自XML文件中的数据反射成对应的struct对象关于反射如何利用struct tag的更多内容请参阅reflect中的相关内容。
@@ -99,6 +104,8 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
- 如果struct的一个字段是string或者[]byte类型且它的tag含有`",innerxml"`Unmarshal将会将此字段所对应的元素内所有内嵌的原始xml累加到此字段上如上面例子Description定义。最后的输出是
```xml
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
@@ -108,6 +115,8 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
<serverIP>127.0.0.2</serverIP>
</server>
```
- 如果struct中有一个叫做XMLName且类型为xml.Name字段那么在解析的时候就会保存这个element的名字到该字段,如上面例子中的servers。
- 如果某个struct字段的tag定义中含有XML结构中element的名称那么解析的时候就会把相应的element值赋值给该字段如上servername和serverip定义。
- 如果某个struct字段的tag定义了中含有`",attr"`那么解析的时候就会将该结构所对应的element的与字段同名的属性的值赋值给该字段如上version定义。
@@ -122,13 +131,15 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
## 输出XML
假若我们不是要解析如上所示的XML文件而是生成它那么在go语言中又该如何实现呢 xml包中提供了`Marshal``MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示:
```Go
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
```
两个函数第一个参数是用来生成XML的结构定义类型数据都是返回生成的XML数据流。
下面我们来看一下如何输出如上的XML
```Go
package main
@@ -161,7 +172,10 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
os.Stdout.Write(output)
}
```
上面的代码输出如下信息:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<servers version="1">
@@ -175,6 +189,7 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
</server>
</servers>
```
和我们之前定义的文件的格式一模一样,之所以会有`os.Stdout.Write([]byte(xml.Header))` 这句代码的出现,是因为`xml.MarshalIndent`或者`xml.Marshal`输出的信息都是不带XML头的为了生成正确的xml文件我们使用了xml包预定义的Header变量。
我们看到`Marshal`函数接收的参数v是interface{}类型的即它可以接受任意类型的参数那么xml包根据什么规则来生成相应的XML文件呢
@@ -204,6 +219,7 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
- tag中含有`"omitempty"`,如果该字段的值为空值那么该字段就不会被输出到XML空值包括false、0、nil指针或nil接口任何长度为0的array, slice, map或者string
- tag中含有`"a>b>c"`那么就会循环输出三个元素a包含bb包含c例如如下代码就会输出
```xml
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
@@ -212,7 +228,7 @@ Go语言的反射机制可以利用这些tag信息来将来自XML文件中的
<last>Xie</last>
</name>
```
上面我们介绍了如何使用Go语言的xml包来编/解码XML文件重要的一点是对XML的所有操作都是通过struct tag来实现的所以学会对struct tag的运用变得非常重要在文章中我们简要的列举了如何定义tag。更多内容或tag定义请参看相应的官方资料。
## links

View File

@@ -2,18 +2,21 @@
JSONJavascript Object Notation是一种轻量级的数据交换语言以文字为基础具有自我描述性且易于让人阅读。尽管JSON是Javascript的一个子集但JSON是独立于语言的文本格式并且采用了类似于C语言家族的一些习惯。JSON与XML最大的不同在于XML是一个完整的标记语言而JSON不是。JSON由于比XML更小、更快更易解析,以及浏览器的内建快速解析支持,使得其更适用于网络数据传输领域。目前我们看到很多的开放平台基本上都是采用了JSON作为他们的数据交互的接口。既然JSON在Web开发中如此重要那么Go语言对JSON支持的怎么样呢Go语言的标准库已经非常好的支持了JSON可以很容易的对JSON数据进行编、解码的工作。
前一小节的运维的例子用json来表示结果描述如下
```json
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
```
本小节余下的内容将以此JSON数据为基础来介绍go语言的json包对JSON数据的编、解码。
## 解析JSON
### 解析到结构体
假如有了上面的JSON串那么我们如何来解析这个JSON串呢Go的JSON包中有如下函数
```Go
func Unmarshal(data []byte, v interface{}) error
```
通过这个函数我们就可以实现解析的目的,详细的解析例子请看如下代码:
```Go
package main
@@ -37,7 +40,7 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
json.Unmarshal([]byte(str), &s)
fmt.Println(s)
}
```
在上面的示例代码中我们首先定义了与json数据对应的结构体数组对应slice字段名对应JSON里面的KEY在解析的时候如何将json数据与struct字段相匹配呢例如JSON的key是`Foo`,那么怎么找对应的字段呢?
- 首先查找tag含有`Foo`的可导出的struct字段(首字母大写)
@@ -57,15 +60,18 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
- nil 代表 JSON null.
现在我们假设有如下的JSON数据
```Go
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
```
如果在我们不知道他的结构的情况下我们把他解析到interface{}里面
```Go
var f interface{}
err := json.Unmarshal(b, &f)
```
这个时候f里面存储了一个map类型他们的key是string值存储在空的interface{}里
```Go
f = map[string]interface{}{
"Name": "Wednesday",
@@ -75,12 +81,14 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
"Morticia",
},
}
```
那么如何来访问这些数据呢?通过断言的方式:
```Go
m := f.(map[string]interface{})
```
通过断言之后,你就可以通过如下方式来访问里面的数据了
```Go
for k, v := range m {
switch vv := v.(type) {
@@ -99,9 +107,11 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
fmt.Println(k, "is of a type I don't know how to handle")
}
}
```
通过上面的示例可以看到通过interface{}与type assert的配合我们就可以解析未知结构的JSON数了。
上面这个是官方提供的解决方案其实很多时候我们通过类型断言操作起来不是很方便目前bitly公司开源了一个叫做`simplejson`的包,在处理未知结构体的JSON时相当方便详细例子如下所示
```Go
js, err := NewJson([]byte(`{
"test": {
@@ -118,14 +128,17 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
i, _ := js.Get("test").Get("int").Int()
ms := js.Get("test").Get("string").MustString()
```
可以看到使用这个库操作JSON比起官方包来说简单的多,详细的请参考如下地址https://github.com/bitly/go-simplejson
## 生成JSON
我们开发很多应用的时候最后都是要输出JSON数据串那么如何来处理呢JSON包里面通过`Marshal`函数来处理,函数定义如下:
```Go
func Marshal(v interface{}) ([]byte, error)
```
假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?请看下面的例子:
```Go
package main
@@ -154,11 +167,14 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
fmt.Println(string(b))
}
```
输出如下内容:
```json
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
```
我们看到上面的输出字段名的首字母都是大写的如果你想用小写的首字母怎么办呢把结构体的字段名改成首字母小写的JSON输出的时候必须注意只有导出的字段才会被输出如果修改字段名那么就会发现什么都不会输出所以必须通过struct tag定义来实现
```Go
type Server struct {
ServerName string `json:"serverName"`
@@ -168,7 +184,7 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
type Serverslice struct {
Servers []Server `json:"servers"`
}
```
通过修改上面的结构体定义输出的JSON串就和我们最开始定义的JSON串保持一致了。
针对JSON的输出我们在定义struct tag的时候需要注意的几点是:
@@ -180,6 +196,7 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
举例来说:
```Go
type Server struct {
// ID 不会导出到JSON中
@@ -202,11 +219,13 @@ JSONJavascript Object Notation是一种轻量级的数据交换语言
b, _ := json.Marshal(s)
os.Stdout.Write(b)
```
会输出以下内容:
```json
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
```
Marshal函数只有在转换成功的时候才会返回数据在转换的过程中我们需要注意几点

View File

@@ -9,14 +9,17 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
## 通过正则判断是否匹配
`regexp`包中含有三个函数用来判断是否匹配如果匹配返回true否则返回false
```Go
func Match(pattern string, b []byte) (matched bool, error error)
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
func MatchString(pattern string, s string) (matched bool, error error)
```
上面的三个函数实现了同一个功能,就是判断`pattern`是否和输入源匹配匹配的话就返回true如果解析正则出错则返回error。三个函数的输入源分别是byte slice、RuneReader和string。
如果要验证一个输入是不是IP地址那么如何来判断呢请看如下实现
```Go
func IsIP(ip string) (b bool) {
if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
@@ -24,8 +27,9 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
}
return true
}
```
可以看到,`regexp`的pattern和我们平常使用的正则一模一样。再来看一个例子当用户输入一个字符串我们想知道是不是一次合法的输入
```Go
func main() {
if len(os.Args) == 1 {
@@ -37,13 +41,14 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果
fmt.Println("不是数字")
}
}
```
在上面的两个小例子中我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。
## 通过正则获取内容
Match模式只能用来对字符串的判断而无法截取字符串的一部分、过滤字符串、或者提取出符合条件的一批字符串。如果想要满足这些需求那就需要使用正则表达式的复杂模式。
我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据:
```Go
package main
@@ -92,18 +97,21 @@ Match模式只能用来对字符串的判断而无法截取字符串的一部
fmt.Println(strings.TrimSpace(src))
}
```
从这个示例可以看出使用复杂的正则首先是Compile它会解析正则表达式是否合法如果正确那么就会返回一个Regexp然后就可以利用返回的Regexp在任意的字符串上面执行需要的操作。
解析正则表达式的有如下几个方法:
```Go
func Compile(expr string) (*Regexp, error)
func CompilePOSIX(expr string) (*Regexp, error)
func MustCompile(str string) *Regexp
func MustCompilePOSIX(str string) *Regexp
```
CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用最左最长方式搜索而Compile是采用的则只采用最左方式搜索(例如[a-z]{2,4}这样一个正则表达式,应用于"aa09aaa88aaaa"这个文本串时CompilePOSIX返回了aaaa而Compile的返回的是aa)。前缀有Must的函数表示在解析正则语法的时候如果匹配模式串不满足正确的语法则直接panic而不加Must的则只是返回错误。
在了解了如何新建一个Regexp之后我们再来看一下这个struct提供了哪些方法来辅助我们操作字符串首先我们来看下面这些用来搜索的函数
```Go
func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
@@ -123,8 +131,9 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int
```
上面这18个函数我们根据输入源(byte slice、string和io.RuneReader)不同还可以继续简化成如下几个,其他的只是输入源不一样,其他功能基本是一样的:
```Go
func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
@@ -134,8 +143,9 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int
```
对于这些函数的使用我们来看下面这个例子
```Go
package main
@@ -189,14 +199,16 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用
submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
fmt.Println(submatchallindex)
}
```
前面介绍过匹配函数Regexp也定义了三个函数它们和同名的外部函数功能一模一样其实外部函数就是调用了这Regexp的三个函数来实现的
```Go
func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool
```
接下里让我们来了解替换函数是怎么操作的?
```Go
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
@@ -204,15 +216,17 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
func (re *Regexp) ReplaceAllString(src, repl string) string
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
```
这些替换函数我们在上面的抓网页的例子有详细应用示例,
接下来我们看一下Expand的解释
```Go
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
```
那么这个Expand到底用来干嘛的呢请看下面的例子
```Go
func main() {
src := []byte(`
@@ -227,7 +241,7 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法它使用
}
fmt.Println(string(res))
}
```
至此我们已经全部介绍完Go语言的`regexp`通过对它的主要函数介绍及演示相信大家应该能够通过Go语言的正则包进行一些基本的正则的操作了。

View File

@@ -12,6 +12,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的不变
## Go模板使用
在Go语言中我们使用`template`包来进行模板处理,使用类似`Parse``ParseFile``Execute`等方法从文件或者字符串加载模板然后执行类似上面图片展示的模板的merge操作。请看下面的例子
```Go
func handler(w http.ResponseWriter, r *http.Request) {
t := template.New("some template") //创建一个模板
@@ -19,7 +20,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的不变
user := GetUser() //获取当前用户信息
t.Execute(w, user) //执行模板的merger操作
}
```
通过上面的例子我们可以看到Go语言的模板操作非常的简单方便和其他语言的模板处理类似都是先获取数据然后渲染数据。
为了演示和测试代码的方便,我们在接下来的例子中采用如下格式的代码
@@ -33,6 +34,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的不变
### 字段操作
Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象这和Java或者C++中的this类似如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子:
```Go
package main
@@ -51,8 +53,9 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
p := Person{UserName: "Astaxie"}
t.Execute(os.Stdout, p)
}
```
上面的代码我们可以正确的输出`hello Astaxie`,但是如果我们稍微修改一下代码,在模板中含有了未导出的字段,那么就会报错
```Go
type Person struct {
UserName string
@@ -60,7 +63,7 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
}
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
```
上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。
如果模板中输出`{{.}}`这个一般应用于字符串对象默认会调用fmt包输出字符串的内容。
@@ -72,6 +75,7 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
- {{with}}操作是指当前对象的值,类似上下文的概念
详细的使用请看下面的例子:
```Go
package main
@@ -109,9 +113,10 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
Friends: []*Friend{&f1, &f2}}
t.Execute(os.Stdout, p)
}
```
### 条件处理
在Go模板里面如果需要进行条件判断那么我们可以使用和Go语言的`if-else`语法类似的方式来处理如果pipeline为空那么if就认为是false下面的例子展示了如何使用`if-else`语法:
```Go
package main
@@ -133,44 +138,54 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n"))
tIfElse.Execute(os.Stdout, nil)
}
```
通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。
> 注意if里面无法使用条件判断例如.Mail=="astaxie@gmail.com"这样的判断是不正确的if里面只能是bool值
### pipelines
Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"beego"的数据表达的意思就是前面的输出可以当做后面的输入最后显示我们想要的数据而Go语言模板最强大的一点就是支持pipe数据在Go语言里面任何`{{}}`里面的都是pipelines数据例如我们上面输出的email里面如果还有一些可能引起XSS注入的那么我们如何来进行转化呢
```Go
{{. | html}}
```
在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体上面的这种方式和我们平常写Unix的方式是不是一模一样操作起来相当的简便调用其他的函数也是类似的方式。
### 模板变量
有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前Go语言通过申明的局部变量格式如下所示
```Go
$variable := pipeline
```
详细的例子看下面的:
```Go
{{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函数关联通过如下的方式来关联
```Go
type FuncMap map[string]interface{}
```
例如如果我们想要的email函数的模板函数名是`emailDeal`它关联的Go函数名称是`EmailDealWith`,那么我们可以通过下面的方式来注册这个函数
```Go
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
```
`EmailDealWith`这个函数的参数和返回值定义如下:
```Go
func EmailDealWith(args interface{}) string
```
我们来看下面的实现例子:
```Go
package main
@@ -230,8 +245,9 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
t.Execute(os.Stdout, p)
}
```
上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面
```Go
var builtins = FuncMap{
"and": and,
@@ -248,9 +264,10 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
"urlquery": URLQueryEscaper,
}
```
## Must操作
模板包里面有一个函数`Must`它的作用是检测模板是否正确例如大括号是否匹配注释是否正确的关闭变量是否正确的书写。接下来我们演示一个例子用Must来判断模板是否正确
```Go
package main
@@ -272,23 +289,29 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
template.Must(tErr.Parse(" some static text {{ .Name }"))
}
```
将输出如下内容
```
The first one parsed OK.
The second one parsed OK.
The next one ought to fail.
panic: template: check parse error with Must:1: unexpected "}" in command
```
## 嵌套模板
我们平常开发Web应用的时候经常会遇到一些模板有些部分是固定不变的然后可以抽取出来作为一个独立的部分例如一个博客的头部和尾部是不变的而唯一改变的是中间的内容部分。所以我们可以定义成`header``content``footer`三个部分。Go语言中通过如下的语法来申明
```Go
{{define "子模板名称"}}内容{{end}}
```
通过如下方式来调用:
```Go
{{template "子模板名称"}}
```
接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl``content.tmpl``footer.tmpl`文件,里面的内容如下
```html
//header.tmpl
{{define "header"}}
@@ -315,8 +338,9 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
</body>
</html>
{{end}}
```
演示代码如下:
```Go
package main
@@ -336,7 +360,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的
fmt.Println()
s1.Execute(os.Stdout, nil)
}
```
通过上面的例子我们可以看到通过`template.ParseFiles`把所有的嵌套模板全部解析到模板里面,其实每一个定义的{{define}}都是一个独立的模板他们相互独立是并行存在的关系内部其实存储的是类似map的一种关系(key是模板的名称value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容我们可以看到header、footer都是相对独立的都能输出内容content 中因为嵌套了header和footer的内容就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。
>同一个集合类的模板是互相知晓的,如果同一模板被多个集合使用,则它需要在多个集合中分别解析

View File

@@ -21,6 +21,7 @@
下面是演示代码:
```Go
package main
@@ -39,6 +40,7 @@
os.RemoveAll("astaxie")
}
```
## 文件操作
@@ -80,6 +82,7 @@
写入string信息到文件
写文件的示例代码
```Go
package main
@@ -102,6 +105,7 @@
}
}
```
### 读文件
读文件函数:
@@ -114,6 +118,7 @@
从off开始读取数据到b中
读文件的示例代码:
```Go
package main
@@ -139,7 +144,8 @@
os.Stdout.Write(buf[:n])
}
}
```
### 删除文件
Go语言里面删除文件和删除文件夹是同一个函数

View File

@@ -6,7 +6,9 @@
- func Contains(s, substr string) bool
字符串s中是否包含substr返回bool值
```Go
fmt.Println(strings.Contains("seafood", "foo"))
fmt.Println(strings.Contains("seafood", "bar"))
fmt.Println(strings.Contains("seafood", ""))
@@ -17,43 +19,56 @@
//true
//true
```
- func Join(a []string, sep string) string
字符串链接把slice a通过sep链接起来
```Go
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
//Output:foo, bar, baz
```
- func Index(s, sep string) int
在字符串s中查找sep所在的位置返回位置值找不到返回-1
```Go
fmt.Println(strings.Index("chicken", "ken"))
fmt.Println(strings.Index("chicken", "dmr"))
//Output:4
//-1
```
- func Repeat(s string, count int) string
重复s字符串count次最后返回重复的字符串
```Go
fmt.Println("ba" + strings.Repeat("na", 2))
//Output:banana
```
- func Replace(s, old, new string, n int) string
在s字符串中把old字符串替换为new字符串n表示替换的次数小于0表示全部替换
```Go
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
//Output:oinky oinky oink
//moo moo moo
```
- func Split(s, sep string) []string
把s字符串按照sep分割返回slice
```Go
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
@@ -62,27 +77,35 @@
//["" "man " "plan " "canal panama"]
//[" " "x" "y" "z" " "]
//[""]
```
- func Trim(s string, cutset string) string
在s字符串的头部和尾部去除cutset指定的字符串
```Go
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
//Output:["Achtung"]
```
- func Fields(s string) []string
去除s字符串的空格符并且按照空格分割返回slice
```Go
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
//Output:Fields are: ["foo" "bar" "baz"]
```
## 字符串转换
字符串转化的函数在strconv中如下也只是列出一些常用的
- Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中。
```Go
package main
import (
@@ -98,8 +121,10 @@
str = strconv.AppendQuoteRune(str, '单')
fmt.Println(string(str))
}
```
- Format 系列函数把其他类型的转换为字符串
```Go
package main
@@ -117,8 +142,12 @@
fmt.Println(a, b, c, d, e)
}
```
- Parse 系列函数把字符串转换为其他类型
```Go
package main
import (
@@ -144,7 +173,7 @@
fmt.Println(a, b, c, d, e)
}
```
## links
* [目录](<preface.md>)

View File

@@ -31,11 +31,15 @@ IPv6是下一版本的互联网协议也可以说是下一代互联网的协
### Go支持的IP类型
在Go的`net`包中定义了很多类型、函数和方法用来网络编程其中IP的定义如下
```Go
type IP []byte
```
`net`包中有很多函数来操作IP但是其中比较有用的也就几个其中`ParseIP(s string) IP`函数会把一个IPv4或者IPv6的地址转化成IP类型请看下面的例子:
```Go
package main
import (
"net"
@@ -57,6 +61,7 @@ IPv6是下一版本的互联网协议也可以说是下一代互联网的协
os.Exit(0)
}
```
执行之后你就会发现只要你输入一个IP地址就会给出相应的IP格式
## TCP Socket
@@ -64,20 +69,29 @@ IPv6是下一版本的互联网协议也可以说是下一代互联网的协
在Go语言的`net`包中有一个类型`TCPConn`,这个类型可以用来作为客户端和服务器端交互的通道,他有两个主要的函数:
```Go
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
```
`TCPConn`可以用在客户端和服务器端来读写数据。
还有我们需要知道一个`TCPAddr`类型他表示一个TCP的地址信息他的定义如下
```Go
type TCPAddr struct {
IP IP
Port int
}
```
在Go语言中通过`ResolveTCPAddr`获取一个`TCPAddr`
```Go
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
```
- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个).
- addr表示域名或者IP地址例如"www.google.com:80" 或者"127.0.0.1:22".
@@ -86,8 +100,10 @@ IPv6是下一版本的互联网协议也可以说是下一代互联网的协
### TCP client
Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回一个`TCPConn`类型的对象,当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的`TCPConn`对象来进行数据交换。一般而言,客户端通过`TCPConn`对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下:
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
```Go
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
```
- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)
- laddr表示本机地址一般设置为nil
- raddr表示远程的服务地址
@@ -97,6 +113,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
"HEAD / HTTP/1.0\r\n\r\n"
从服务端接收到的响应信息格式可能如下:
```Go
HTTP/1.0 200 OK
ETag: "-9985996"
@@ -105,8 +122,9 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
Connection: close
Date: Sat, 28 Aug 2010 00:43:48 GMT
Server: lighttpd/1.4.23
```
我们的客户端代码如下所示:
```Go
package main
@@ -141,15 +159,18 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
}
}
```
通过上面的代码我们可以看出:首先程序将用户的输入作为参数`service`传入`net.ResolveTCPAddr`获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接`conn`,通过`conn`来发送请求信息,最后通过`ioutil.ReadAll``conn`中读取全部的文本,也就是服务端响应反馈的信息。
### TCP server
上面我们编写了一个TCP的客户端程序也可以通过net包来创建一个服务器端程序在服务器端我们需要绑定服务到指定的非激活端口并监听此端口当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数函数定义如下
```Go
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
```
参数说明同DialTCP的参数一样。下面我们实现一个简单的时间同步服务监听7777端口
```Go
package main
@@ -183,9 +204,11 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
}
}
```
上面的服务跑起来之后,它将会一直在那里等待,直到有新的客户端请求到达。当有新的客户端请求到达并同意接受`Accept`该请求的时候他会反馈当前的时间信息。值得注意的是,在代码中`for`循环里当有错误发生时直接continue而不是退出是因为在服务器端跑代码的时候当有错误发生的情况下最好是由服务端记录错误然后当前连接的客户端直接报错而退出从而不会影响到当前服务端运行的整个服务。
上面的代码有个缺点执行的时候是单任务的不能同时接收多个请求那么该如何改造以使它支持多并发呢Go里面有一个goroutine机制请看下面改造后的代码
```Go
package main
@@ -224,9 +247,11 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
}
}
```
通过把业务处理分离到函数`handleClient`,我们就可以进一步地实现多并发执行了。看上去是不是很帅,增加`go`关键词就实现了服务端的多并发从这个小例子也可以看出goroutine的强大之处。
有的朋友可能要问:这个服务端没有处理客户端实际请求的内容。如果我们需要通过从客户端发送不同的请求来获取不同的时间格式,而且需要一个长连接,该怎么做呢?请看:
```Go
package main
@@ -287,34 +312,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接并返回
}
}
```
在上面这个例子中,我们使用`conn.Read()`不断读取客户端发来的请求。由于我们需要保持与客户端的长连接,所以不能在读取完一次请求后就关闭连接。由于`conn.SetReadDeadline()`设置了超时,当一定时间内客户端无请求发送,`conn`便会自动关闭下面的for循环即会因为连接已关闭而跳出。需要注意的是`request`在创建时需要指定一个最大长度以防止flood attack每次读取到请求处理完毕后需要清理request因为`conn.Read()`会将新读取到的内容append到原内容之后。
### 控制TCP连接
TCP有很多连接控制函数我们平常用到比较多的有如下几个函数
```Go
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
```
设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。
```Go
func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error
```
用来设置写入/读取一个连接的超时时间。当超过设置时间时,连接自动关闭。
```Go
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
```
设置keepAlive属性是操作系统层在tcp上没有数据和ACK的时候会间隔性的发送keepalive包操作系统可以通过该包来判断一个tcp连接是否已经断开在windows上默认2个小时没有收到数据和keepalive包的时候人为tcp连接已经断开这个功能和我们通常在应用层加的心跳包的功能类似。
更多的内容请查看`net`包的文档。
## UDP Socket
Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样只有TCP换成了UDP而已。UDP的几个主要函数如下所示
```Go
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error)
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)
```
一个UDP的客户端代码如下所示,我们可以看到不同的就是TCP换成了UDP而已
```Go
package main
@@ -349,7 +383,9 @@ Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端
}
}
```
我们来看一下UDP服务器端如何来处理
```Go
package main
@@ -386,6 +422,7 @@ Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端
}
}
```
## 总结
通过对TCP和UDP Socket编程的描述和实现可见Go已经完备地支持了Socket编程而且使用起来相当的方便Go提供了很多函数通过这些函数可以很容易就编写出高性能的Socket应用。

View File

@@ -45,6 +45,8 @@ Go语言标准包里面没有提供对WebSocket的支持但是在由官方维
WebSocket分为客户端和服务端接下来我们将实现一个简单的例子:用户输入信息客户端通过WebSocket将信息发送给服务器端服务器端收到信息之后主动Push信息到客户端然后客户端将输出其收到的信息客户端的代码如下
```html
<html>
<head></head>
<body>
@@ -86,7 +88,7 @@ WebSocket分为客户端和服务端接下来我们将实现一个简单的
</body>
</html>
```
可以看到客户端JS很容易的就通过WebSocket函数建立了一个与服务器的连接sock当握手成功后会触发WebScoket对象的onopen事件告诉客户端连接已经成功建立。客户端一共绑定了四个事件。
- 1onopen 建立连接后触发
@@ -96,6 +98,8 @@ WebSocket分为客户端和服务端接下来我们将实现一个简单的
我们服务器端的实现如下:
```Go
package main
import (
@@ -136,6 +140,7 @@ WebSocket分为客户端和服务端接下来我们将实现一个简单的
}
}
```
当客户端将用户输入的信息Send之后服务器端通过Receive接收到了相应信息然后通过Send发送了应答信息。
![](images/8.2.websocket3.png?raw=true)

View File

@@ -62,6 +62,8 @@ Go没有为REST提供直接支持但是因为RESTful是基于HTTP协议实现
我们现在可以通过`POST`里面增加隐藏字段`_method`这种方式可以来模拟`PUT``DELETE`等方式但是服务器端需要做转换。我现在的项目里面就按照这种方式来做的REST接口。当然Go语言里面完全按照RESTful来实现是很容易的我们通过下面的例子来说明如何实现RESTful的应用设计。
```Go
package main
import (
@@ -103,6 +105,7 @@ Go没有为REST提供直接支持但是因为RESTful是基于HTTP协议实现
http.ListenAndServe(":8088", nil)
}
```
上面的代码演示了如何编写一个REST的应用我们访问的资源是用户我们通过不同的method来访问不同的函数这里使用了第三方库`github.com/drone/routes`在前面章节我们介绍过如何实现自定义的路由器这个库实现了自定义路由和方便的路由规则映射通过它我们可以很方便的实现REST的架构。通过上面的代码可知REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。
## 总结

View File

@@ -45,6 +45,8 @@ T、T1和T2类型必须能被`encoding/gob`包编解码。
### HTTP RPC
http的服务端代码实现如下
```Go
package main
import (
@@ -90,10 +92,13 @@ http的服务端代码实现如下
}
}
```
通过上面的例子可以看到我们注册了一个Arith的RPC服务然后通过`rpc.HandleHTTP`函数把该服务注册到了HTTP协议上然后我们就可以利用http的方式来传递数据了。
请看下面的客户端代码:
```Go
package main
import (
@@ -140,15 +145,19 @@ http的服务端代码实现如下
}
```
我们把上面的服务端和客户端的代码分别编译,然后先把服务端开启,然后开启客户端,输入代码,就会输出如下信息:
```Go
$ ./http_c localhost
Arith: 17*8=136
Arith: 17/8=2 remainder 1
```
通过上面的调用可以看到参数和返回值是我们定义的struct类型在服务端我们把它们当做调用函数的参数的类型在客户端作为`client.Call`的第23两个参数的类型。客户端最重要的就是这个Call函数它有3个参数第1个要调用的函数的名字第2个是要传递的参数第3个要返回的参数(注意是指针类型)通过上面的代码例子我们可以发现使用Go的RPC实现相当的简单方便。
### TCP RPC
上面我们实现了基于HTTP协议的RPC接下来我们要实现基于TCP协议的RPC服务端的实现代码如下所示
```Go
package main
@@ -212,11 +221,14 @@ http的服务端代码实现如下
}
}
```
上面这个代码和http的服务器相比不同在于:在此处我们采用了TCP协议然后需要自己控制连接当有客户端连接上来后我们需要把这个连接交给rpc来处理。
如果你留心了你会发现这它是一个阻塞型的单用户的程序如果想要实现多并发那么可以使用goroutine来实现我们前面在socket小节的时候已经介绍过如何处理goroutine。
下面展现了TCP实现的RPC客户端
```Go
package main
import (
@@ -263,11 +275,14 @@ http的服务端代码实现如下
}
```
这个客户端代码和http的客户端代码对比唯一的区别一个是DialHTTP一个是Dial(tcp),其他处理一模一样。
### JSON RPC
JSON RPC是数据编码采用了JSON而不是gob编码其他和上面介绍的RPC概念一模一样下面我们来演示一下如何使用Go提供的json-rpc标准包请看服务端代码的实现
```Go
package main
import (
@@ -331,9 +346,11 @@ JSON RPC是数据编码采用了JSON而不是gob编码其他和上面介
}
}
```
通过示例我们可以看出 json-rpc是基于TCP协议实现的目前它还不支持HTTP方式。
请看客户端的实现代码:
```Go
package main
@@ -380,7 +397,8 @@ JSON RPC是数据编码采用了JSON而不是gob编码其他和上面介
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
```
## 总结
Go已经提供了对RPC的良好支持通过上面HTTP、TCP、JSON RPC的实现,我们就可以很方便的开发很多分布式的Web应用我想作为读者的你已经领会到这一点。但遗憾的是目前Go尚未提供对SOAP RPC的支持欣慰的是现在已经有第三方的开源实现了。

View File

@@ -49,9 +49,12 @@ CSRF的防御可以从服务端和客户端两方面着手防御效果是从
接下来我就以Go语言来举例说明如何限制对资源的访问方法
```Go
mux.Get("/user/:uid", getuser)
mux.Post("/user/:uid", modifyuser)
```
这样处理后因为我们限定了修改只能使用POST当GET方式请求时就拒绝响应所以上面图示中GET方式的CSRF攻击就可以防止了但这样就能全部解决问题了吗当然不是因为POST也是可以模拟的。
因此我们需要实施第二步在非GET方式的请求中增加随机数这个大概有三种方式来进行
@@ -62,6 +65,8 @@ CSRF的防御可以从服务端和客户端两方面着手防御效果是从
生成随机数token
```Go
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
io.WriteString(h, "ganraomaxxxxxxxxx")
@@ -70,12 +75,17 @@ CSRF的防御可以从服务端和客户端两方面着手防御效果是从
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, token)
```
输出token
```html
<input type="hidden" name="token" value="{{.}}">
```
验证token
```Go
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
@@ -83,7 +93,8 @@ CSRF的防御可以从服务端和客户端两方面着手防御效果是从
} else {
//不存在token报错
}
```
这样基本就实现了安全的POST但是也许你会说如果破解了token的算法呢按照理论上是但是实际上破解是基本不可能的因为有人曾计算过暴力破解该串大概需要2的11次方时间。
## 总结

View File

@@ -31,6 +31,7 @@
- 加入检查及阻止来自外部数据源的变量命名为CleanMap。
接下来,让我们通过一个例子来巩固这些概念,请看下面这个表单
```html
<form action="/whoami" method="POST">
我是谁:
@@ -42,7 +43,9 @@
<input type="submit" />
</form>
```
在处理这个表单的编程逻辑中非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟POST操作递交`name=attack`这样的数据,所以在此时我们需要做类似白名单的处理
```Go
r.ParseForm()
name := r.Form.Get("name")
@@ -51,10 +54,12 @@
CleanMap["name"] = name
}
```
上面代码中我们初始化了一个CleanMap的变量当判断获取的name是`astaxie``herry``marry`三个中的一个之后
我们把数据存储到了CleanMap之中这样就可以确保CleanMap["name"]中的数据是合法的从而在代码的其它部分使用它。当然我们还可以在else部分增加非法数据的处理一种可能是再次显示表单并提示错误。但是不要试图为了友好而输出被污染的数据。
上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成:
```Go
r.ParseForm()
username := r.Form.Get("username")
@@ -62,7 +67,8 @@
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok {
CleanMap["username"] = username
}
```
## 总结
数据过滤在Web安全中起到一个基石的作用大多数的安全问题都是由于没有过滤数据和验证数据引起的例如前面小节的CSRF攻击以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的因此我们需要特别重视这部分的内容。

View File

@@ -38,11 +38,13 @@ Web应用未对用户提交请求的数据做充分的检查过滤允许用
- 使用HTTP头指定类型
```Go
`w.Header().Set("Content-Type","text/javascript")`
这样就可以让浏览器解析javascript代码而不会是html输出
```
## 总结
XSS漏洞是相当有危害的在开发Web应用的时候一定要记住过滤数据特别是在输出到客户端之前这是现在行之有效的防止XSS的手段。

View File

@@ -9,6 +9,7 @@ SQL注入攻击SQL Injection简称注入攻击是Web开发中最常
下面将通过一些真实的例子来详细讲解SQL注入的方式。
考虑以下简单的登录表单:
```html
<form action="/login" method="POST">
<p>Username: <input type="text" name="username" /></p>
@@ -16,31 +17,39 @@ SQL注入攻击SQL Injection简称注入攻击是Web开发中最常
<p><input type="submit" value="登陆" /></p>
</form>
```
我们的处理里面的SQL可能是这样的
```Go
username:=r.Form.Get("username")
password:=r.Form.Get("password")
sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'"
```
如果用户的输入的用户名如下,密码任意
```Go
myuser' or 'foo' = 'foo' --
```
那么我们的SQL变成了如下所示
```Go
SELECT * FROM user WHERE username='myuser' or 'foo'=='foo' --'' AND password='xxx'
```
在SQL里面`--`是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。
对于MSSQL还有更加危险的一种SQL注入就是控制系统下面这个可怕的例子将演示如何在某些版本的MSSQL数据库上执行系统命令。
```Go
sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'"
Db.Exec(sql)
```
如果攻击提交`a%' exec master..xp_cmdshell 'net user test testpass /ADD' --`作为变量 prod的值那么sql将会变成
```Go
sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"
```
MSSQL服务器会执行这条SQL语句包括它后面那个用于向系统添加新用户的命令。如果这个程序是以sa运行而 MSSQLSERVER服务又有足够的权限的话攻击者就可以获得一个系统帐号来访问主机了。
>虽然以上的例子是针对某一特定的数据库系统的,但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞,只要使用不同方法,各种数据库都有可能遭殃。

View File

@@ -7,6 +7,7 @@
目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256, SHA-1, MD5等。
Go语言对这三种加密算法的实现如下所示
```Go
//import "crypto/sha256"
h := sha256.New()
@@ -23,6 +24,7 @@ Go语言对这三种加密算法的实现如下所示
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))
```
单向哈希有两个特性:
- 1同一个密码进行单向哈希得到的总是唯一确定的摘要。
@@ -40,6 +42,8 @@ Go语言对这三种加密算法的实现如下所示
没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是先将用户输入的密码进行一次MD5或其它哈希算法加密将得到的 MD5 值前后加上一些只有管理员自己知道的随机串再进行一次MD5加密。这个随机串中可以包括某些固定的串也可以包括用户名用来保证每个用户加密使用的密钥都不一样
```Go
//import "crypto/md5"
//假设用户名abc密码123456
h := md5.New()
@@ -60,6 +64,7 @@ Go语言对这三种加密算法的实现如下所示
last :=fmt.Sprintf("%x", h.Sum(nil))
```
在两个salt没有泄露的情况下黑客如果拿到的是最后这个加密串就几乎不可能推算出原始的密码是什么了。
## 专家方案
@@ -72,9 +77,10 @@ Go语言对这三种加密算法的实现如下所示
这里推荐`scrypt`方案scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。
目前Go语言里面支持的库http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
```Go
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
```
通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。
## 总结

View File

@@ -4,6 +4,8 @@
## base64加解密
如果Web应用足够简单数据的安全性没有那么严格的要求那么可以采用一种比较简单的加解密方法是`base64`这种方式实现起来比较简单Go语言的`base64`包已经很好的支持了这个,请看下面的例子:
```Go
package main
import (
@@ -37,7 +39,7 @@
fmt.Println(string(enbyte))
}
```
## 高级加解密
Go语言的`crypto`里面支持对称加密的高级加解密包有:
@@ -46,6 +48,7 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有:
- `crypto/des`DES(Data Encryption Standard)是一种对称加密标准是目前使用最广泛的密钥系统特别是在保护金融数据的安全中。曾是美国联邦政府的加密标准但现已被AES所替代。
因为这两种算法使用方法类似所以在此我们仅用aes包为例来讲解它们的使用请看下面的例子
```Go
package main
@@ -94,9 +97,11 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有:
fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy)
}
```
上面通过调用函数`aes.NewCipher`(参数key必须是16、24或者32位的[]byte分别对应AES-128, AES-192或AES-256算法),返回了一个`cipher.Block`接口,这个接口实现了三个功能:
```Go
type Block interface {
// BlockSize returns the cipher's block size.
BlockSize() int
@@ -109,7 +114,7 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有:
// Dst and src may point at the same memory.
Decrypt(dst, src []byte)
}
```
这三个函数实现了加解密操作,详细的操作请看上面的例子。
## 总结

View File

@@ -19,6 +19,7 @@ GO语言默认采用"UTF-8"编码集所以我们实现i18n时不考虑第三
- 有利于搜索引擎抓取能够提高站点的SEO
我们可以通过下面的代码来实现域名的对应locale
```Go
if r.Host == "www.asta.com" {
i18n.SetLocale("en")
@@ -27,8 +28,9 @@ GO语言默认采用"UTF-8"编码集所以我们实现i18n时不考虑第三
} else if r.Host == "www.asta.tw" {
i18n.SetLocale("zh-TW")
}
```
当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示:
```Go
prefix := strings.Split(r.Host,".")
@@ -39,7 +41,7 @@ GO语言默认采用"UTF-8"编码集所以我们实现i18n时不考虑第三
} else if prefix[0] == "tw" {
i18n.SetLocale("zh-TW")
}
```
通过域名设置Locale有如上所示的优点但是我们一般开发Web应用的时候不会采用这种方式因为首先域名成本比较高开发一个Locale就需要一个域名而且往往统一名称的域名不一定能申请的到其次我们不愿意为每个站点去本地化一个配置而更多的是采用url后面带参数的方式请看下面的介绍。
### 从域名参数设置Locale
@@ -48,15 +50,17 @@ GO语言默认采用"UTF-8"编码集所以我们实现i18n时不考虑第三
这种设置方式几乎拥有前面讲的通过域名设置Locale的所有优点它采用RESTful的方式以使得我们不需要增加额外的方法来处理。但是这种方式需要在每一个的link里面增加相应的参数locale这也许有点复杂而且有时候甚至相当的繁琐。不过我们可以写一个通用的函数url让所有的link地址都通过这个函数来生成然后在这个函数里面增加`locale=params["locale"]`参数来缓解一下。
也许我们希望URL地址看上去更加的RESTful一点例如www.asta.com/en/books(英文站点)和www.asta.com/zh/books(中文站点)这种方式的URL更加有利于SEO而且对于用户也比较友好能够通过URL直观的知道访问的站点。那么这样的URL地址可以通过router来获取locale(参考REST小节里面介绍的router插件实现)
```Go
mux.Get("/:locale/books", listbook)
```
### 从客户端设置地区
在一些特殊的情况下我们需要根据客户端的信息而不是通过URL来设置Locale这些信息可能来自于客户端设置的喜好语言(浏览器中设置)用户的IP地址用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。
- Accept-Language
客户端请求的时候在HTTP头信息里面有`Accept-Language`一般的客户端都会设置该信息下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码:
```Go
AL := r.Header.Get("Accept-Language")
if AL == "en" {
@@ -66,7 +70,7 @@ GO语言默认采用"UTF-8"编码集所以我们实现i18n时不考虑第三
} else if AL == "zh-TW" {
i18n.SetLocale("zh-TW")
}
```
当然在实际应用中,可能需要更加严格的判断来进行设置地区
- IP地址

View File

@@ -4,6 +4,8 @@
## 本地化文本消息
文本信息是编写Web应用中最常用到的也是本地化资源中最多的信息想要以适合本地语言的方式来显示文本信息可行的一种方案是:建立需要的语言相应的map来维护一个key-value的关系在输出之前按需从适合的map中去获取相应的文本如下是一个简单的示例
```Go
package main
import "fmt"
@@ -34,16 +36,17 @@
return ""
}
```
上面示例演示了不同locale的文本翻译实现了中文和英文对于同一个key显示不同语言的实现上面实现了中文的文本消息如果想切换到英文版本只需要把lang设置为en即可。
有些时候仅是key-value替换是不能满足需要的例如"I am 30 years old",中文表达是"我今年30岁了"而此处的30是一个变量该怎么办呢这个时候我们可以结合`fmt.Printf`函数来实现,请看下面的代码:
```Go
en["how old"] ="I am %d years old"
cn["how old"] ="我今年%d岁了"
fmt.Printf(msg(lang, "how old"), 30)
```
上面的示例代码仅用以演示内部的实现方案而实际数据是存储在JSON里面的所以我们可以通过`json.Unmarshal`来为相应的map填充数据。
## 本地化日期和时间
@@ -54,6 +57,8 @@
$GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义为了获得对应于当前locale的时间我们应首先使用`time.LoadLocation(name string)`获取相应于地区的locale比如`Asia/Shanghai``America/Chicago`对应的时区信息,然后再利用此信息与调用`time.Now`获得的Time对象协作来获得最终的时间。详细的请看下面的例子(该例子采用上面例子的一些变量):
```Go
en["time_zone"]="America/Chicago"
cn["time_zone"]="Asia/Shanghai"
@@ -62,7 +67,9 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义
t = t.In(loc)
fmt.Println(t.Format(time.RFC3339))
```
我们可以通过类似处理文本格式的方式来解决时间格式的问题,举例如下:
```Go
en["date_format"]="%Y-%m-%d %H:%M:%S"
cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒"
@@ -78,8 +85,10 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义
//%d 替换成24
}
```
## 本地化货币值
各个地区的货币表示也不一样,处理方式也与日期差不多,细节请看下面代码:
```Go
en["money"] ="USD %d"
cn["money"] ="¥%d元"
@@ -90,9 +99,10 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义
return fmt.Sprintf(fomate,money)
}
```
## 本地化视图和资源
我们可能会根据Locale的不同来展示视图这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢首先我们应按locale来组织文件信息请看下面的文件目录安排
```html
views
|--en //英文模板
@@ -108,14 +118,16 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义
index.tpl
login.tpl
```
有了这个目录结构后我们就可以在渲染的地方这样来实现代码:
```Go
s1, _ := template.ParseFiles("views"+lang+"index.tpl")
VV.Lang=lang
s1.Execute(os.Stdout, VV)
```
而对于里面的index.tpl里面的资源设置如下
```html
// js文件
<script type="text/javascript" src="views/{{.VV.Lang}}/js/jquery/jquery-1.8.0.min.js"></script>
@@ -123,7 +135,7 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义
<link href="views/{{.VV.Lang}}/css/bootstrap-responsive.min.css" rel="stylesheet">
// 图片文件
<img src="views/{{.VV.Lang}}/images/btn.png">
```
采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。
## 总结

View File

@@ -3,6 +3,7 @@
前面小节介绍了如何处理本地化资源即Locale一个相应的配置文件那么如果处理多个的本地化资源呢而对于一些我们经常用到的例如简单的文本翻译、时间日期、数字等如果处理呢本小节将一一解决这些问题。
## 管理多个本地包
在开发一个应用的时候首先我们要决定是只支持一种语言还是多种语言如果要支持多种语言我们则需要制定一个组织结构以方便将来更多语言的添加。在此我们设计如下Locale有关的文件放置在config/locales下假设你要支持中文和英文那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示
```json
# zh.json
@@ -22,22 +23,26 @@
}
}
```
为了支持国际化,在此我们使用了一个国际化相关的包——[go-i18n](https://github.com/astaxie/go-i18n)首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件
```Go
Tr:=i18n.NewLocale()
Tr.LoadPath("config/locales")
```
这个包使用起来很简单,你可以通过下面的方式进行测试:
```Go
fmt.Println(Tr.Translate("submit"))
//输出Submit
Tr.SetLocale("zn")
fmt.Println(Tr.Translate("submit"))
//输出“递交”
```
## 自动加载本地包
上面我们介绍了如何自动加载自定义语言包其实go-i18n库已经预加载了很多默认的格式信息例如时间格式、货币格式用户可以在自定义配置时改写这些默认配置请看下面的处理过程
```Go
//加载默认配置文件这些文件都放在go-i18n/locales下面
@@ -83,7 +88,9 @@
return nil
}
```
通过上面的方法加载配置信息到默认的文件,这样我们就可以在我们没有自定义时间信息的时候执行如下的代码获取对应的信息:
```Go
//locale=zh的情况下执行如下代码
@@ -95,13 +102,14 @@
fmt.Println(Tr.Money(11.11))
//输出:¥11.11
```
## template mapfunc
上面我们实现了多个语言包的管理和加载,而一些函数的实现是基于逻辑层的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等虽然我们在逻辑层可以利用这些函数把需要的参数进行转换后在模板层渲染的时候直接输出但是如果我们想在模版层直接使用这些函数该怎么实现呢不知你是否还记得在前面介绍模板的时候说过Go语言的模板支持自定义模板函数下面是我们实现的方便操作的mapfunc
1. 文本信息
文本信息调用`Tr.Translate`来实现相应的信息转换mapFunc的实现如下
```Go
func I18nT(args ...interface{}) string {
ok := false
@@ -115,18 +123,22 @@
return Tr.Translate(s)
}
```
注册函数如下:
```Go
t.Funcs(template.FuncMap{"T": I18nT})
```
模板中使用如下:
```Go
{{.V.Submit | T}}
```
2. 时间日期
时间日期调用`Tr.Time`函数来实现相应的时间转换mapFunc的实现如下
```Go
func I18nTimeDate(args ...interface{}) string {
ok := false
@@ -139,18 +151,21 @@
}
return Tr.Time(s)
}
```
注册函数如下:
```Go
t.Funcs(template.FuncMap{"TD": I18nTimeDate})
```
模板中使用如下:
```Go
{{.V.Now | TD}}
```
3. 货币信息
货币调用`Tr.Money`函数来实现相应的时间转换mapFunc的实现如下
```Go
func I18nMoney(args ...interface{}) string {
ok := false
@@ -163,15 +178,17 @@
}
return Tr.Money(s)
}
```
注册函数如下:
```Go
t.Funcs(template.FuncMap{"M": I18nMoney})
```
模板中使用如下:
```Go
{{.V.Money | M}}
```
## 总结
通过这小节我们知道了如何实现一个多语言包的Web应用通过自定义语言包我们可以方便的实现多语言而且通过配置文件能够非常方便的扩充多语言默认情况下go-i18n会自定加载一些公共的配置信息例如时间、货币等我们就可以非常方便的使用同时为了支持在模板中使用这些函数也实现了相应的模板函数这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。

View File

@@ -1,24 +1,28 @@
# 11.1 错误处理
Go语言主要的设计准则是简洁、明白简洁是指语法和C类似相当的简单明白是指任何语句都是很明显的不含有任何隐含的东西在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误但是对于使用者来说不查看相应的API说明文档根本搞不清楚这个返回值究竟代表什么意思比如:返回0是成功还是失败,而Go定义了一个叫做error的类型来显式表达错误。在使用时通过把返回的error变量与nil的比较来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量
```Go
func Open(name string) (file *File, err error)
```
下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息:
```Go
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
```
类似于`os.Open`函数标准包中所有可能出错的API都会返回一个error变量以方便错误处理这个小节将详细地介绍error类型的设计和讨论开发Web应用中如何更好地处理error。
## Error类型
error类型是一个接口类型这是它的定义
```Go
type error interface {
Error() string
}
```
error是一个内置的接口类型我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString
```Go
// errorString is a trivial implementation of error.
type errorString struct {
@@ -28,15 +32,17 @@ error是一个内置的接口类型我们可以在/builtin/包下面找到相
func (e *errorString) Error() string {
return e.s
}
```
你可以通过`errors.New`把一个字符串转化为errorString以得到一个满足接口error的对象其内部实现如下
```Go
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
```
下面这个例子演示了如何使用`errors.New`:
```Go
func Sqrt(f float64) (float64, error) {
if f < 0 {
@@ -44,16 +50,18 @@ error是一个内置的接口类型我们可以在/builtin/包下面找到相
}
// implementation
}
```
在下面的例子中我们在调用Sqrt的时候传递的一个负数然后就得到了non-nil的error对象将此对象与nil比较结果为true所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
```Go
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
```
## 自定义Error
通过上面的介绍我们知道error是一个interface所以在实现自己的包的时候通过定义实现此接口的结构我们就可以实现自己的错误定义请看来自Json包的示例
```Go
type SyntaxError struct {
msg string // 错误描述
@@ -61,8 +69,9 @@ error是一个内置的接口类型我们可以在/builtin/包下面找到相
}
func (e *SyntaxError) Error() string { return e.msg }
```
Offset字段在调用Error的时候不会被打印但是我们可以通过类型断言获取错误类型然后可以打印相应的错误信息请看下面的例子:
```Go
if err := dec.Decode(&val); err != nil {
if serr, ok := err.(*json.SyntaxError); ok {
@@ -71,8 +80,9 @@ Offset字段在调用Error的时候不会被打印但是我们可以通过类
}
return err
}
```
需要注意的是函数返回自定义错误时返回值推荐设置为error类型而非自定义错误类型特别需要注意的是不应预声明自定义错误类型的变量。例如
```Go
func Decode() *SyntaxError { // 错误将可能导致上层调用者err!=nil的判断永远为true。
var err *SyntaxError // 预声明错误变量
@@ -81,10 +91,11 @@ Offset字段在调用Error的时候不会被打印但是我们可以通过类
}
return err // 错误err永远等于非nil导致上层调用者err!=nil的判断始终为true
}
```
原因见 http://golang.org/doc/faq#nil_error
上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢此时我们来参考一下net包采用的方法
```Go
package net
@@ -94,7 +105,9 @@ Offset字段在调用Error的时候不会被打印但是我们可以通过类
Temporary() bool // Is the error temporary?
}
```
在调用的地方通过类型断言err是不是net.Error,来细化错误的处理例如下面的例子如果一个网络发生临时性错误那么将会sleep 1秒之后重试
```Go
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
@@ -103,11 +116,12 @@ Offset字段在调用Error的时候不会被打印但是我们可以通过类
if err != nil {
log.Fatal(err)
}
```
## 错误处理
Go在错误处理上采用了与C类似的检查返回值的方式而不是其他多数主流语言采用的异常方式这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。
请看下面这个例子代码:
```Go
func init() {
http.HandleFunc("/view", viewRecord)
@@ -125,8 +139,9 @@ Go在错误处理上采用了与C类似的检查返回值的方式而不是
http.Error(w, err.Error(), 500)
}
}
```
上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`返回给客户端500错误码并显示相应的错误数据。但是当越来越多的HandleFunc加入之后这样的错误处理逻辑代码就会越来越多其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。
```Go
type appHandler func(http.ResponseWriter, *http.Request) error
@@ -135,14 +150,16 @@ Go在错误处理上采用了与C类似的检查返回值的方式而不是
http.Error(w, err.Error(), 500)
}
}
```
上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数:
```Go
func init() {
http.Handle("/view", appHandler(viewRecord))
}
```
当请求/view的时候我们的逻辑处理可以变成如下代码和第一种实现方式相比较已经简单了很多。
```Go
func viewRecord(w http.ResponseWriter, r *http.Request) error {
c := appengine.NewContext(r)
@@ -153,16 +170,18 @@ Go在错误处理上采用了与C类似的检查返回值的方式而不是
}
return viewTemplate.Execute(w, record)
}
```
上面的例子错误处理的时候所有的错误返回给用户的都是500错误码然后打印出来相应的错误代码其实我们可以把这个错误信息定义的更加友好调试的时候也方便定位问题我们可以自定义返回的错误类型
```Go
type appError struct {
Error error
Message string
Code int
}
```
这样我们的自定义路由器可以改成如下方式:
```Go
type appHandler func(http.ResponseWriter, *http.Request) *appError
@@ -173,8 +192,9 @@ Go在错误处理上采用了与C类似的检查返回值的方式而不是
http.Error(w, e.Message, e.Code)
}
}
```
这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:
```Go
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
c := appengine.NewContext(r)
@@ -188,7 +208,7 @@ Go在错误处理上采用了与C类似的检查返回值的方式而不是
}
return nil
}
```
如上所示在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息虽然这个和第一个版本的代码量差不多但是这个显示的错误更加明显提示的错误信息更加友好扩展性也比第一个更好。
## 总结

View File

@@ -1,6 +1,8 @@
# 11.2 使用GDB调试
开发程序过程中调试代码是开发者经常要做的一件事情Go语言不像PHP、Python等动态语言只要修改不需要编译就可以直接输出而且可以动态的在运行环境下打印数据。当然Go语言也可以通过Println之类的打印数据来调试但是每次都需要重新编译这是一件相当麻烦的事情。我们知道在Python中有pdb/ipdb之类的工具调试Javascript也有类似工具这些工具都能够动态的显示变量信息单步调试等。不过庆幸的是Go也有类似的工具支持GDB。Go内部已经内置支持了GDB所以我们可以通过GDB来进行调试那么本小节就来介绍一下如何通过GDB来调试Go程序。
另外建议纯go代码使用[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试
## GDB调试简介
GDB是FSF(自由软件基金会)发布的一个强大的类UNIX系统下的程序调试工具。使用GDB可以做如下事情
@@ -94,6 +96,7 @@ GDB的一些常用命令如下所示
## 调试过程
我们通过下面这个代码来演示如何通过GDB来调试Go程序下面是将要演示的代码
```Go
package main
@@ -120,7 +123,7 @@ GDB的一些常用命令如下所示
fmt.Println("count:", count)
}
}
```
编译文件生成可执行文件gdbfile:
go build -gcflags "-N -l" gdbfile.go

View File

@@ -3,6 +3,13 @@
Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`命令来实现单元测试和性能测试,`testing`框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。
另外建议安装[gotests](https://github.com/cweill/gotests)插件自动生成测试代码:
```Go
go get -u -v github.com/cweill/gotests/...
```
## 如何编写测试用例
由于`go test`命令只能在一个相应的目录下执行所有文件,所以我们接下来新建一个项目目录`gotest`,这样我们所有的代码和测试代码都在这个目录下。
@@ -10,6 +17,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
1. gotest.go:这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:
```Go
package gotest
import (
@@ -24,6 +33,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
return a / b, nil
}
```
2. gotest_test.go:这是我们的单元测试文件,但是记住下面的这些原则:
- 文件名必须是`_test.go`结尾的,这样在执行`go test`的时候才会执行到相应的代码
@@ -36,6 +47,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
下面是我们的测试用例的代码:
```Go
package gotest
import (
@@ -54,6 +67,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
t.Error("就是不通过")
}
```
我们在项目目录下面执行`go test`,就会显示如下信息:
--- FAIL: Test_Division_2 (0.00 seconds)
@@ -73,7 +88,9 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
exit status 1
FAIL gotest 0.012s
上面的输出详细的展示了这个测试的过程我们看到测试函数1`Test_Division_1`测试通过而测试函数2`Test_Division_2`测试失败了最后得出结论测试不通过。接下来我们把测试函数2修改成如下代码
```Go
func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function
t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错
@@ -81,6 +98,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
t.Log("one test passed.", e) //记录一些你期望记录的信息
}
}
```
然后我们执行`go test -v`,就显示如下信息,测试通过了:
=== RUN Test_Division_1
@@ -97,14 +115,18 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
- 压力测试用例必须遵循如下格式其中XXX可以是任意字母数字的组合但是首字母不能是小写字母
```Go
func BenchmarkXXX(b *testing.B) { ... }
```
- `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数
- 在压力测试用例中,请记得在循环体内使用`testing.B.N`,以使测试可以正常的运行
- 文件名也必须以`_test.go`结尾
下面我们新建一个压力测试文件webbench_test.go代码如下所示
```Go
package gotest
import (
@@ -129,6 +151,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
}
}
```
我们执行命令`go test -file webbench_test.go -test.bench=".*"`,可以看到如下结果:

View File

@@ -1,6 +1,8 @@
# 12.1 应用日志
我们期望开发的Web应用程序能够把整个程序运行过程中出现的各种事件一一记录下来Go语言中提供了一个简易的log包我们使用该包可以方便的实现日志记录的功能这些日志都是基于fmt包的打印再结合panic之类的函数来进行一般的打印、抛出错误处理。Go目前标准包只是包含了简单的功能如果我们想把我们的应用日志保存到文件然后又能够结合日志实现很多复杂的功能编写过Java或者C++的读者应该都使用过log4j和log4cpp之类的日志工具可以使用第三方开发的一个日志系统`https://github.com/cihub/seelog`,它实现了很强大的日志功能。接下来我们介绍如何通过该日志系统来实现我们应用的日志功能。
[logrus](https://github.com/sirupsen/logrus)是另外一个不错的日志系统,结合自己项目选择
## seelog介绍
seelog是用Go语言实现的一个日志系统它提供了一些简单的函数来实现复杂的日志分配、过滤和格式化。主要有如下特性
@@ -18,10 +20,13 @@ seelog是用Go语言实现的一个日志系统它提供了一些简单的函
上面只列举了部分特性seelog是一个特别强大的日志处理系统详细的内容请参看官方wiki。接下来我将简要介绍一下如何在项目中使用它
首先安装seelog
```Go
go get -u github.com/cihub/seelog
```
然后我们来看一个简单的例子:
```Go
package main
@@ -32,10 +37,12 @@ seelog是用Go语言实现的一个日志系统它提供了一些简单的函
log.Info("Hello from Seelog!")
}
```
编译后运行如果出现了`Hello from seelog`说明seelog日志系统已经成功安装并且可以正常运行了。
## 基于seelog的自定义日志处理
seelog支持自定义日志处理下面是我基于它自定义的日志处理包的部分内容
```Go
package logs
@@ -90,7 +97,7 @@ seelog支持自定义日志处理下面是我基于它自定义的日志处
func UseLogger(newLogger seelog.LoggerInterface) {
Logger = newLogger
}
```
上面主要实现了三个函数,
- `DisableLog`
@@ -116,6 +123,7 @@ seelog支持自定义日志处理下面是我基于它自定义的日志处
设置当前的日志器为相应的日志处理
上面我们定义了一个自定义的日志处理包,下面就是使用示例:
```Go
package main
@@ -132,29 +140,32 @@ seelog支持自定义日志处理下面是我基于它自定义的日志处
err := http.ListenAndServe(addr, routes.NewMux())
logs.Logger.Critical("Server err:%v", err)
}
```
## 发生错误发送邮件
上面的例子解释了如何设置发送邮件我们通过如下的smtp配置用来发送邮件
```html
<smtp formatid="criticalemail" senderaddress="astaxie@gmail.com" sendername="ShortUrl API" hostname="smtp.gmail.com" hostport="587" username="mailusername" password="mailpassword">
<recipient address="xiemengjun@gmail.com"/>
</smtp>
```
邮件的格式通过criticalemail配置然后通过其他的配置发送邮件服务器的配置通过recipient配置接收邮件的用户如果有多个用户可以再添加一行。
要测试这个代码是否正常工作,可以在代码中增加类似下面的一个假消息。不过记住过后要把它删除,否则上线之后就会收到很多垃圾邮件。
```Go
logs.Logger.Critical("test Critical message")
```
现在只要我们的应用在线上记录一个Critical的信息你的邮箱就会收到一个Email这样一旦线上的系统出现问题你就能立马通过邮件获知就能及时的进行处理。
## 使用应用日志
对于应用日志,每个人的应用场景可能会各不相同,有些人利用应用日志来做数据分析,有些人利用应用日志来做性能分析,有些人来做用户行为分析,还有些就是纯粹的记录,以方便应用出现问题的时候辅助查找问题。
举一个例子,我们需要跟踪用户尝试登陆系统的操作。这里会把成功与不成功的尝试都记录下来。记录成功的使用"Info"日志级别,而不成功的使用"warn"级别。如果想查找所有不成功的登陆我们可以利用linux的grep之类的命令工具如下
```Go
# cat /data/logs/roll.log | grep "failed login"
2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password
```
通过这种方式我们就可以很方便的查找相应的信息这样有利于我们针对应用日志做一些统计和分析。另外我们还需要考虑日志的大小对于一个高流量的Web应用来说日志的增长是相当可怕的所以我们在seelog的配置文件里面设置了logrotate这样就能保证日志文件不会因为不断变大而导致我们的磁盘空间不够引起问题。
## 小结

View File

@@ -31,6 +31,8 @@
通知用户在访问页面的时候我们可以有两种错误404.html和error.html下面分别显示了错误页面的源码
```html
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
@@ -51,8 +53,11 @@
</div>
</body>
</html>
```
另一个源码:
```html
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
@@ -74,8 +79,11 @@
</body>
</html>
```
404的错误处理逻辑如果是系统的错误也是类似的操作同时我们看到在
```Go
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
@@ -99,10 +107,12 @@
t.Execute(w, ErrorInfo) //执行模板的merger操作
}
```
## 如何处理异常
我们知道在很多其他语言中有try...catch关键词用来捕获异常情况但是其实很多错误都是可以预期发生的而不需要异常处理应该当做错误来处理这也是为什么Go语言采用了函数返回错误的设计这些函数不会panic例如如果一个文件找不到os.Open返回一个错误它不会panic如果你向一个中断的网络连接写数据net.Conn系列类型的Write函数返回一个错误它们不会panic。这些状态在这样的程序里都是可以预期的。你知道这些操作可能会失败因为设计者已经用返回错误清楚地表明了这一点。这就是上面所讲的可以预期发生的错误。
但是还有一种情况有一些操作几乎不可能失败而且在一些特定的情况下也没有办法返回错误也无法继续执行这样情况就应该panic。举个例子如果一个程序计算x[j]但是j越界了这部分代码就会导致panic像这样的一个不可预期严重错误就会引起panic在默认情况下它会杀掉进程它允许一个正在运行这部分代码的goroutine从发生错误的panic中恢复运行发生panic之后这部分代码后面的函数和代码都不会继续执行这是Go特意这样设计的因为要区别于错误和异常panic其实就是异常处理。如下代码我们期望通过uid来获取User中的username信息但是如果uid越界了就会抛出异常这个时候如果我们没有recover机制进程就会被杀死从而导致程序不可服务。因此为了程序的健壮性在一些地方需要建立recover机制。
```Go
func GetUser(uid int) (username string) {
defer func() {
@@ -114,7 +124,7 @@
username = User[uid]
return
}
```
上面介绍了错误和异常的区别那么我们在开发程序的时候如何来设计呢规则很简单如果你定义的函数有可能失败它就应该返回一个错误。当我调用其他package的函数时如果这个函数实现的很好我不需要担心它会panic除非有真正的异常情况发生即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑针对一些特殊情况来设计。
## 小结

View File

@@ -7,6 +7,8 @@
- MarGo的一个实现思路使用Commond来执行自身的应用如果真想实现那么推荐这种方案
```Go
d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
if *d {
cmd := exec.Command(os.Args[0],
@@ -32,8 +34,10 @@
cmd.Process.Kill()
}
}
```
- 另一种是利用syscall的方案但是这个方案并不完善
```Go
package main
@@ -103,7 +107,8 @@
return 0
}
```
上面提出了两种实现Go的daemon方案但是我还是不推荐大家这样去实现因为官方还没有正式的宣布支持daemon当然第一种方案目前来看是比较可行的而且目前开源库skynet也在采用这个方案做daemon。
## Supervisord
@@ -121,6 +126,8 @@ Supervisord可以通过`sudo easy_install supervisor`安装,当然也可以通
### Supervisord配置
Supervisord默认的配置文件路径为/etc/supervisord.conf通过文本编辑器修改这个文件下面是一个示例的配置文件
```conf
;/etc/supervisord.conf
[unix_http_server]
file = /var/run/supervisord.sock
@@ -161,6 +168,7 @@ Supervisord默认的配置文件路径为/etc/supervisord.conf通过文本编
redirect_stderr = true
stdout_logfile = /var/log/supervisord/blogdemon.log
```
### Supervisord管理
Supervisord安装完成后有两个可用的命令行supervisor和supervisorctl命令使用解释如下

View File

@@ -9,6 +9,7 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st
路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。
## 默认的路由实现
在3.4小节有过介绍Go的http包的详解里面介绍了Go的http包如何设计和实现路由这里继续以一个例子来说明
```Go
func fooHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
@@ -21,7 +22,8 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st
})
log.Fatal(http.ListenAndServe(":8080", nil))
```
上面的例子调用了http默认的DefaultServeMux来添加路由需要提供两个参数第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点:
- 添加路由信息
@@ -30,6 +32,7 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st
Go默认的路由添加是通过函数`http.Handle``http.HandleFunc`等来添加,底层都是调用了`DefaultServeMux.Handle(pattern string, handler Handler)`,这个函数会把路由信息存储在一个map信息中`map[string]muxEntry`,这就解决了上面说的第一点。
Go监听端口然后接收到tcp连接会扔给Handler来处理上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度遍历之前存储的map路由信息和用户访问的URL进行匹配以查询对应注册的处理函数这样就实现了上面所说的第二点。
```Go
for k, v := range mux.m {
if !pathMatch(k, path) {
@@ -40,7 +43,7 @@ Go监听端口然后接收到tcp连接会扔给Handler来处理上面的
h = v.h
}
}
```
## beego框架路由实现
目前几乎所有的Web应用路由实现都是基于http默认的路由器但是Go自带的路由器有几个限制
@@ -55,6 +58,7 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方
针对前面所说的限制点我们首先要解决参数支持就需要用到正则第二和第三点我们通过一种变通的方法来解决REST的方法对应到struct的方法中去然后路由到struct而不是函数这样在转发路由的时候就可以根据method来执行不同的方法。
根据上面的思路我们设计了两个数据类型controllerInfo(保存路径和对应的struct这里是一个reflect.Type类型)和ControllerRegistor(routers是一个slice用来保存用户添加的路由信息以及beego框架的应用信息)
```Go
type controllerInfo struct {
regex *regexp.Regexp
@@ -67,12 +71,14 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方
Application *App
}
```
ControllerRegistor对外的接口函数有
```Go
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface)
```
详细的实现如下所示:
```Go
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
parts := strings.Split(pattern, "/")
@@ -118,22 +124,25 @@ ControllerRegistor对外的接口函数有
p.routers = append(p.routers, route)
}
```
### 静态路由实现
上面我们实现的动态路由的实现Go的http包默认支持静态文件处理FileServer由于我们实现了自定义的路由器那么静态文件也需要自己设定beego的静态文件夹路径保存在全局变量StaticDir中StaticDir是一个map类型实现如下
```Go
func (app *App) SetStaticPath(url string, path string) *App {
StaticDir[url] = path
return app
}
```
应用中设置静态路径可以使用如下方式实现:
```Go
beego.SetStaticPath("/img","/static/img")
```
### 转发路由
转发路由是基于ControllerRegistor里的路由信息来进行转发的详细的实现如下代码所示
```Go
// AutoRoute
func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -242,22 +251,25 @@ ControllerRegistor对外的接口函数有
http.NotFound(w, r)
}
}
```
### 使用入门
基于这样的路由设计之后就可以解决前面所说的三个限制点,使用的方式如下所示:
基本的使用注册路由:
```Go
beego.BeeApp.RegisterController("/", &controllers.MainController{})
```
参数注册:
```Go
beego.BeeApp.RegisterController("/:param", &controllers.UserController{})
```
正则匹配:
```Go
beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{})
```
## links
* [目录](<preface.md>)
* 上一章: [项目规划](<13.1.md>)

View File

@@ -8,6 +8,7 @@ MVC设计模式是目前Web应用开发中最常见的架构模式通过分
## beego的REST设计
前面小节介绍了路由实现了注册struct的功能而struct中实现了REST方式因此我们需要设计一个用于逻辑处理controller的基类这里主要设计了两个类型一个struct、一个interface
```Go
type Controller struct {
Ct *Context
@@ -32,8 +33,9 @@ MVC设计模式是目前Web应用开发中最常见的架构模式通过分
Finish() //执行完成之后的处理
Render() error //执行完method对应的方法之后渲染页面
}
```
那么前面介绍的路由add函数的时候是定义了ControllerInterface类型因此只要我们实现这个接口就可以所以我们的基类Controller实现如下的方法
```Go
func (c *Controller) Init(ct *Context, cn string) {
c.Data = make(map[interface{}]interface{})
@@ -113,17 +115,19 @@ MVC设计模式是目前Web应用开发中最常见的架构模式通过分
func (c *Controller) Redirect(url string, code int) {
c.Ct.Redirect(code, url)
}
```
上面的controller基类已经实现了接口定义的函数通过路由根据url执行相应的controller的原则会依次执行如下
```Go
Init() 初始化
Prepare() 执行之前的初始化每个继承的子类可以来实现该函数
method() 根据不同的method执行不同的函数GETPOSTPUTHEAD等子类来实现这些函数如果没实现那么默认都是403
Render() 可选根据全局变量AutoRender来判断是否执行
Finish() 执行完之后执行的操作每个继承的子类可以来实现该函数
```
## 应用指南
上面beego框架中完成了controller基类的设计那么我们在我们的应用中可以这样来设计我们的方法
```Go
package controllers
@@ -140,12 +144,13 @@ MVC设计模式是目前Web应用开发中最常见的架构模式通过分
this.Data["Email"] = "astaxie@gmail.com"
this.TplNames = "index.tpl"
}
```
上面的方式我们实现了子类MainController实现了Get方法那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回403而如果是Get来访问因为我们设置了AutoRender=true那么在执行完Get方法之后会自动执行Render函数就会显示如下界面
![](images/13.4.beego.png?raw=true)
index.tpl的代码如下所示我们可以看到数据的设置和显示都是相当的简单方便
```html
<!DOCTYPE html>
<html>
@@ -156,7 +161,8 @@ index.tpl的代码如下所示我们可以看到数据的设置和显示都
<h1>Hello, world!{{.Username}},{{.Email}}</h1>
</body>
</html>
```
## links
* [目录](<preface.md>)

View File

@@ -8,6 +8,7 @@
## beego的日志设计
beego的日志设计部署思路来自于seelog根据不同的level来记录日志但是beego设计的日志系统比较轻量级采用了系统的log.Logger接口默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出详细的实现如下所示
```Go
// Log levels to control the logging output.
const (
@@ -33,9 +34,10 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
func SetLevel(l int) {
level = l
}
```
上面这一段实现了日志系统的日志分级默认的级别是Trace用户通过SetLevel可以设置不同的分级。
```Go
// logger references the used application logger.
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
@@ -85,7 +87,7 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
BeeLogger.Printf("[C] %v\n", v)
}
}
```
上面这一段代码默认初始化了一个BeeLogger对象默认输出到os.Stdout用户可以通过beego.SetLogger来设置实现了logger的接口输出。这里面实现了六个函数
- Trace一般的记录信息举例如下
@@ -117,6 +119,7 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
配置信息的解析beego实现了一个key=value的配置文件读取类似ini配置文件的格式就是一个文件解析的过程然后把解析的数据保存到map中最后在调用的时候通过几个string、int之类的函数调用返回相应的值具体的实现请看下面
首先定义了一些ini配置文件的一些全局性常量
```Go
var (
bComment = []byte{'#'}
@@ -124,9 +127,10 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
bEqual = []byte{'='}
bDQuote = []byte{'"'}
)
```
定义了配置文件的格式:
```Go
// A Config represents the configuration.
type Config struct {
filename string
@@ -135,9 +139,10 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
offset map[string]int64 // key: offset; for editing.
sync.RWMutex
}
```
定义了解析文件的函数解析文件的过程是打开文件然后一行一行的读取解析注释、空行和key=value数据
```Go
// ParseFile creates a new Config and parses the file configuration from the
// named file.
func LoadConfig(name string) (*Config, error) {
@@ -196,9 +201,10 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
}
return cfg, nil
}
```
下面实现了一些读取配置文件的函数返回的值确定为bool、int、float64或string
```Go
// Bool returns the boolean value for a given key.
func (c *Config) Bool(key string) (bool, error) {
return strconv.ParseBool(c.data[key])
@@ -218,9 +224,10 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
func (c *Config) String(key string) string {
return c.data[key]
}
```
## 应用指南
下面这个函数是我一个应用中的例子用来获取远程url地址的json数据实现如下
```Go
func GetJson() {
resp, err := http.Get(beego.AppConfig.String("url"))
@@ -235,13 +242,14 @@ beego的日志设计部署思路来自于seelog根据不同的level来记录
beego.Critical("error:", err)
}
}
```
函数中调用了框架的日志函数`beego.Critical`函数用来报错,调用了`beego.AppConfig.String("url")`用来获取配置文件中的信息,配置文件的信息如下(app.conf)
```Go
appname = hs
url ="http://www.api.com/api.html"
```
## links
* [目录](<preface.md>)
* 上一章: [controller设计](<13.3.md>)

View File

@@ -24,6 +24,7 @@
## 博客路由
博客主要的路由规则如下所示:
```Go
//显示博客首页
beego.Router("/", &controllers.IndexController{})
@@ -36,9 +37,10 @@
//编辑博文
beego.Router("/edit/:id([0-9]+)", &controllers.EditController{})
```
## 数据库结构
数据库设计最简单的博客信息
```sql
CREATE TABLE entries (
id INT AUTO_INCREMENT,
@@ -47,10 +49,12 @@
created DATETIME,
primary key (id)
);
```
## 控制器
IndexController:
```Go
type IndexController struct {
beego.Controller
}
@@ -60,9 +64,11 @@ IndexController:
this.Layout = "layout.tpl"
this.TplNames = "index.tpl"
}
```
ViewController:
```Go
type ViewController struct {
beego.Controller
}
@@ -73,8 +79,9 @@ ViewController:
this.Layout = "layout.tpl"
this.TplNames = "view.tpl"
}
```
NewController
```Go
type NewController struct {
beego.Controller
@@ -94,8 +101,9 @@ NewController
models.SaveBlog(blog)
this.Ctx.Redirect(302, "/")
}
```
EditController
```Go
type EditController struct {
beego.Controller
@@ -118,8 +126,9 @@ EditController
models.SaveBlog(blog)
this.Ctx.Redirect(302, "/")
}
```
DeleteController
```Go
type DeleteController struct {
beego.Controller
@@ -132,8 +141,9 @@ DeleteController
models.DelBlog(blog)
this.Ctx.Redirect(302, "/")
}
```
## model层
```Go
package models
@@ -183,11 +193,12 @@ DeleteController
db.Delete(&blog)
return
}
```
## view层
layout.tpl
```html
<html>
<head>
@@ -210,8 +221,10 @@ layout.tpl
</body>
</html>
```
index.tpl
```html
<h1>Blog posts</h1>
@@ -225,15 +238,17 @@ index.tpl
</li>
{{end}}
</ul>
```
view.tpl
```html
<h1>{{.Post.Title}}</h1>
{{.Post.Created}}<br/>
{{.Post.Content}}
```
new.tpl
```html
<h1>New Blog Post</h1>
<form action="" method="post">
@@ -241,8 +256,9 @@ new.tpl
内容:<textarea name="content" colspan="3" rowspan="10"></textarea>
<input type="submit">
</form>
```
edit.tpl
```html
<h1>Edit {{.Post.Title}}</h1>
@@ -253,7 +269,7 @@ edit.tpl
<input type="hidden" name="id" value="{{.Post.Id}}">
<input type="submit">
</form>
```
## links
* [目录](<preface.md>)
* 上一章: [日志和配置设计](<13.4.md>)

View File

@@ -3,6 +3,7 @@
## beego静态文件实现和设置
Go的net/http包中提供了静态文件的服务`ServeFile``FileServer`等函数。beego的静态文件处理就是基于这一层处理的具体的实现如下所示
```Go
//static file server
for prefix, staticDir := range StaticDir {
@@ -13,13 +14,14 @@ Go的net/http包中提供了静态文件的服务`ServeFile`和`FileServer`
return
}
}
```
StaticDir里面保存的是相应的url对应到静态文件所在的目录因此在处理URL请求的时候只需要判断对应的请求地址是否包含静态处理开头的url如果包含的话就采用http.ServeFile提供服务。
举例如下:
```Go
beego.StaticDir["/asset"] = "/static"
```
那么请求url如`http://www.beego.me/asset/bootstrap.css`就会请求`/static/bootstrap.css`来提供反馈给客户端。
## bootstrap集成
@@ -47,11 +49,14 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对
图14.2 项目中静态文件目录结构
2. 因为beego默认设置了StaticDir的值所以如果你的静态文件目录是static的话就无须再增加了
```Go
StaticDir["/static"] = "static"
```
3. 模板中使用如下的地址就可以了:
```html
//css文件
<link href="/static/css/bootstrap.css" rel="stylesheet">
@@ -60,7 +65,7 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对
//图片文件
<img src="/static/img/logo.png">
```
上面可以实现把bootstrap集成到beego中来如下展示的图就是集成进来之后的展现效果图
![](images/14.1.bootstrap3.png?raw=true)

View File

@@ -3,6 +3,7 @@
## session集成
beego中主要有以下的全局变量来控制session处理
```Go
//related to session
SessionOn bool // 是否开启session模块默认不开启
@@ -11,8 +12,9 @@ beego中主要有以下的全局变量来控制session处理
SessionGCMaxLifetime int64 // cookies有效期
GlobalSessions *session.Manager //全局session控制器
```
当然上面这些变量需要初始化值,也可以按照下面的代码来配合配置文件以设置这些值:
```Go
if ar, err := AppConfig.Bool("sessionon"); err != nil {
SessionOn = false
@@ -35,32 +37,36 @@ beego中主要有以下的全局变量来控制session处理
} else {
SessionGCMaxLifetime = 3600
}
```
在beego.Run函数中增加如下代码
```Go
if SessionOn {
GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime)
go GlobalSessions.GC()
}
```
这样只要SessionOn设置为true那么就会默认开启session功能独立开一个goroutine来处理session。
为了方便我们在自定义Controller中快速使用session作者在`beego.Controller`中提供了如下方法:
```Go
func (c *Controller) StartSession() (sess session.Session) {
sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request)
return
}
```
## session使用
通过上面的代码我们可以看到beego框架简单地继承了session功能那么在项目中如何使用呢
首先我们需要在应用的main入口处开启session
```Go
beego.SessionOn = true
```
然后我们就可以在控制器的相应方法中如下所示的使用session了
```Go
func (this *MainController) Get() {
var intcount int
@@ -78,22 +84,27 @@ beego中主要有以下的全局变量来控制session处理
this.Data["Count"] = intcount
this.TplNames = "index.tpl"
}
```
上面的代码展示了如何在控制逻辑中使用session主要分两个步骤
1. 获取session对象
```Go
//获取对象,类似PHP中的session_start()
sess := this.StartSession()
```
2. 使用session进行一般的session值操作
```Go
//获取session值类似PHP中的$_SESSION["count"]
sess.Get("count")
//设置session值
sess.Set("count", intcount)
```
从上面代码可以看出基于beego框架开发的应用中使用session相当方便基本上和PHP中调用`session_start()`类似。

View File

@@ -21,6 +21,7 @@
对于开发者来说一般开发过程都是相当复杂而且大多是在重复一样的工作。假设一个场景项目中忽然需要增加一个表单数据那么局部代码的整个流程都需要修改。我们知道Go里面struct是常用的一个数据结构因此beego的form采用了struct来处理表单信息。
首先定义一个开发Web应用时相对应的struct一个字段对应一个form元素通过struct的tag来定义相应的元素信息和验证信息如下所示
```Go
type User struct{
Username string `form:text,valid:required`
@@ -29,23 +30,26 @@
Email string `form:text,valid:required|valid_email`
Introduce string `form:textarea`
}
```
定义好struct之后接下来在controller中这样操作
```Go
func (this *AddController) Get() {
this.Data["form"] = beego.Form(&User{})
this.Layout = "admin/layout.html"
this.TplNames = "admin/add.tpl"
}
```
在模板中这样显示表单
```html
<h1>New Blog Post</h1>
<form action="" method="post">
{{.form.render()}}
</form>
```
上面我们定义好了整个的第一步从struct到显示表单的过程接下来就是用户填写信息服务器端接收数据然后验证最后插入数据库。
```Go
func (this *AddController) Post() {
var user User
@@ -56,7 +60,7 @@
models.UserInsert(&user)
this.Ctx.Redirect(302, "/admin/index")
}
```
## 表单类型
以下列表列出来了对应的form元素信息
<table cellpadding="0" cellspacing="1" border="0" style="width:100%" class="tableborder">

View File

@@ -9,10 +9,12 @@ beego目前没有针对这三种方式进行任何形式的集成但是可以
## HTTP Basic和 HTTP Digest认证
这两个认证是一些应用采用的比较简单的认证,目前已经有开源的第三方库支持这两个认证:
github.com/abbot/go-http-auth
```Go
github.com/abbot/go-http-auth
```
下面代码演示了如何把这个库引入beego中从而实现认证
```Go
package controllers
@@ -45,22 +47,25 @@ beego目前没有针对这三种方式进行任何形式的集成但是可以
this.Data["Email"] = "astaxie@gmail.com"
this.TplNames = "index.tpl"
}
```
上面代码利用了beego的prepare函数在执行正常逻辑之前调用了认证函数这样就非常简单的实现了http authdigest的认证也是同样的原理。
## oauth和oauth2的认证
oauth和oauth2是目前比较流行的两种认证方式还好第三方有一个库实现了这个认证但是是国外实现的并没有QQ、微博之类的国内应用认证集成
```Go
github.com/bradrydzewski/go.auth
```
下面代码演示了如何把该库引入beego中从而实现oauth的认证这里以github为例演示
1. 添加两条路由
```Go
beego.RegisterController("/auth/login", &controllers.GithubController{})
beego.RegisterController("/mainpage", &controllers.PageController{})
```
2. 然后我们处理GithubController登陆的页面
```Go
package controllers
@@ -89,8 +94,9 @@ oauth和oauth2是目前比较流行的两种认证方式还好第三方有一
githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
}
```
3. 处理登陆成功之后的页面
```Go
package controllers
@@ -126,7 +132,7 @@ oauth和oauth2是目前比较流行的两种认证方式还好第三方有一
this.Data["name"] = user.Name()
this.TplNames = "home.tpl"
}
```
整个的流程如下,首先打开浏览器输入地址:
![](images/14.4.github.png?raw=true)
@@ -147,7 +153,7 @@ oauth和oauth2是目前比较流行的两种认证方式还好第三方有一
## 自定义认证
自定义的认证一般都是和session结合验证的如下代码来源于一个基于beego的开源博客
```Go
//登陆处理
func (this *LoginController) Post() {
@@ -239,8 +245,9 @@ oauth和oauth2是目前比较流行的两种认证方式还好第三方有一
}
return true
}
```
有了用户登陆和注册之后,其他模块的地方可以增加如下这样的用户是否登陆的判断:
```Go
func (this *AddBlogController) Prepare() {
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
@@ -252,7 +259,7 @@ oauth和oauth2是目前比较流行的两种认证方式还好第三方有一
}
this.Data["Username"] = sess_username
}
```
## links
* [目录](<preface.md>)
* 上一节: [表单及验证支持](<14.3.md>)

View File

@@ -4,20 +4,23 @@
## i18n集成
beego中设置全局变量如下
```Go
Translation i18n.IL
Lang string //设置语言包zh、en
LangPath string //设置语言包所在位置
```
初始化多语言函数:
```Go
func InitLang(){
beego.Translation:=i18n.NewLocale()
beego.Translation.LoadPath(beego.LangPath)
beego.Translation.SetLocale(beego.Lang)
}
```
为了方便在模板中直接调用多语言包,我们设计了三个函数来处理响应的多语言:
```Go
beegoTplFuncMap["Trans"] = i18n.I18nT
beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate
@@ -58,17 +61,19 @@ beego中设置全局变量如下
}
return beego.Translation.Money(s)
}
```
## 多语言开发使用
1. 设置语言以及语言包所在位置然后初始化i18n对象
```Go
beego.Lang = "zh"
beego.LangPath = "views/lang"
beego.InitLang()
```
2. 设计多语言包
上面讲了如何初始化多语言包现在设计多语言包多语言包是json文件如第十章介绍的一样我们需要把设计的文件放在LangPath下面例如zh.json或者en.json
```json
# zh.json
@@ -87,17 +92,19 @@ beego中设置全局变量如下
"create": "Create"
}
}
```
3. 使用语言包
我们可以在controller中调用翻译获取响应的翻译语言如下所示
```Go
func (this *MainController) Get() {
this.Data["create"] = beego.Translation.Translate("create")
this.TplNames = "index.tpl"
}
```
我们也可以在模板中直接调用响应的翻译函数:
```Go
//直接文本翻译
{{.create | Trans}}
@@ -107,7 +114,7 @@ beego中设置全局变量如下
//货币翻译
{{.money | TransMoney}}
```
## links
* [目录](<preface.md>)
* 上一节: [用户认证](<14.4.md>)

View File

@@ -1,23 +1,26 @@
# 14.6 pprof支持
Go语言有一个非常棒的设计就是标准库里面带有代码的性能监控工具在两个地方有包
```Go
net/http/pprof
runtime/pprof
```
其实net/http/pprof中只是使用runtime/pprof包来进行封装了一下并在http端口上暴露出来
## beego支持pprof
目前beego框架新增了pprof该特性默认是不开启的如果你需要测试性能查看相应的执行goroutine之类的信息其实Go的默认包"net/http/pprof"已经具有该功能如果按照Go默认的方式执行Web默认就可以使用但是由于beego重新封装了ServHTTP函数默认的包是无法开启该功能的所以需要对beego的内部改造支持pprof。
- 首先在beego.Run函数中根据变量是否自动加载性能包
```Go
if PprofOn {
BeeApp.RegisterController(`/debug/pprof`, &ProfController{})
BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{})
}
```
- 设计ProfConterller
```Go
package beego
@@ -45,13 +48,14 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监
this.Ctx.ResponseWriter.WriteHeader(200)
}
```
## 使用入门
通过上面的设计你可以通过如下代码开启pprof
```Go
beego.PprofOn = true
```
然后你就可以在浏览器中打开如下URL就看到如下界面
![](images/14.6.pprof.png?raw=true)
@@ -64,9 +68,10 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监
图14.8 显示当前goroutine的详细信息
我们还可以通过命令行获取更多详细的信息
```Go
go tool pprof http://localhost:8080/debug/pprof/profile
```
这时候程序就会进入30秒的profile收集时间在这段时间内拼命刷新浏览器上的页面尽量让cpu占用性能产生数据。
(pprof) top10