Merge pull request #856 from vCaesar/u18-pr
Format and remove zh/x.md spaces
This commit is contained in:
450
zh/08.1.md
450
zh/08.1.md
@@ -33,33 +33,33 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协
|
||||
在Go的`net`包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下:
|
||||
```Go
|
||||
|
||||
type IP []byte
|
||||
type IP []byte
|
||||
|
||||
```
|
||||
在`net`包中有很多函数来操作IP,但是其中比较有用的也就几个,其中`ParseIP(s string) IP`函数会把一个IPv4或者IPv6的地址转化成IP类型,请看下面的例子:
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"fmt"
|
||||
)
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
name := os.Args[1]
|
||||
addr := net.ParseIP(name)
|
||||
if addr == nil {
|
||||
fmt.Println("Invalid address")
|
||||
} else {
|
||||
fmt.Println("The address is ", addr.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
package main
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"fmt"
|
||||
)
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
name := os.Args[1]
|
||||
addr := net.ParseIP(name)
|
||||
if addr == nil {
|
||||
fmt.Println("Invalid address")
|
||||
} else {
|
||||
fmt.Println("The address is ", addr.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
```
|
||||
执行之后你就会发现只要你输入一个IP地址就会给出相应的IP格式
|
||||
@@ -71,8 +71,8 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协
|
||||
|
||||
```Go
|
||||
|
||||
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
|
||||
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
|
||||
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
|
||||
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
|
||||
```
|
||||
|
||||
`TCPConn`可以用在客户端和服务器端来读写数据。
|
||||
@@ -81,16 +81,16 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协
|
||||
|
||||
```Go
|
||||
|
||||
type TCPAddr struct {
|
||||
IP IP
|
||||
Port int
|
||||
}
|
||||
type TCPAddr struct {
|
||||
IP IP
|
||||
Port int
|
||||
}
|
||||
```
|
||||
在Go语言中通过`ResolveTCPAddr`获取一个`TCPAddr`
|
||||
|
||||
```Go
|
||||
|
||||
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
|
||||
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
|
||||
```
|
||||
|
||||
- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个).
|
||||
@@ -102,7 +102,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
|
||||
```Go
|
||||
|
||||
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
|
||||
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
|
||||
@@ -115,49 +115,49 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
从服务端接收到的响应信息格式可能如下:
|
||||
```Go
|
||||
|
||||
HTTP/1.0 200 OK
|
||||
ETag: "-9985996"
|
||||
Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT
|
||||
Content-Length: 18074
|
||||
Connection: close
|
||||
Date: Sat, 28 Aug 2010 00:43:48 GMT
|
||||
Server: lighttpd/1.4.23
|
||||
HTTP/1.0 200 OK
|
||||
ETag: "-9985996"
|
||||
Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT
|
||||
Content-Length: 18074
|
||||
Connection: close
|
||||
Date: Sat, 28 Aug 2010 00:43:48 GMT
|
||||
Server: lighttpd/1.4.23
|
||||
```
|
||||
我们的客户端代码如下所示:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||
checkError(err)
|
||||
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
|
||||
checkError(err)
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
checkError(err)
|
||||
fmt.Println(string(result))
|
||||
os.Exit(0)
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||
checkError(err)
|
||||
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
|
||||
checkError(err)
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
checkError(err)
|
||||
fmt.Println(string(result))
|
||||
os.Exit(0)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
通过上面的代码我们可以看出:首先程序将用户的输入作为参数`service`传入`net.ResolveTCPAddr`获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接`conn`,通过`conn`来发送请求信息,最后通过`ioutil.ReadAll`从`conn`中读取全部的文本,也就是服务端响应反馈的信息。
|
||||
@@ -166,43 +166,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
上面我们编写了一个TCP的客户端程序,也可以通过net包来创建一个服务器端程序,在服务器端我们需要绑定服务到指定的非激活端口,并监听此端口,当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数,函数定义如下:
|
||||
```Go
|
||||
|
||||
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
|
||||
func (l *TCPListener) Accept() (c Conn, err os.Error)
|
||||
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
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := ":7777"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
daytime := time.Now().String()
|
||||
conn.Write([]byte(daytime)) // don't care about return value
|
||||
conn.Close() // we're finished with this client
|
||||
}
|
||||
}
|
||||
func checkError(err error) {
|
||||
func main() {
|
||||
service := ":7777"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
continue
|
||||
}
|
||||
daytime := time.Now().String()
|
||||
conn.Write([]byte(daytime)) // don't care about return value
|
||||
conn.Close() // we're finished with this client
|
||||
}
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
上面的服务跑起来之后,它将会一直在那里等待,直到有新的客户端请求到达。当有新的客户端请求到达并同意接受`Accept`该请求的时候他会反馈当前的时间信息。值得注意的是,在代码中`for`循环里,当有错误发生时,直接continue而不是退出,是因为在服务器端跑代码的时候,当有错误发生的情况下最好是由服务端记录错误,然后当前连接的客户端直接报错而退出,从而不会影响到当前服务端运行的整个服务。
|
||||
@@ -210,42 +210,42 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
上面的代码有个缺点,执行的时候是单任务的,不能同时接收多个请求,那么该如何改造以使它支持多并发呢?Go里面有一个goroutine机制,请看下面改造后的代码
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := ":1200"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
daytime := time.Now().String()
|
||||
conn.Write([]byte(daytime)) // don't care about return value
|
||||
// we're finished with this client
|
||||
}
|
||||
func checkError(err error) {
|
||||
func main() {
|
||||
service := ":1200"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
continue
|
||||
}
|
||||
go handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
daytime := time.Now().String()
|
||||
conn.Write([]byte(daytime)) // don't care about return value
|
||||
// we're finished with this client
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
通过把业务处理分离到函数`handleClient`,我们就可以进一步地实现多并发执行了。看上去是不是很帅,增加`go`关键词就实现了服务端的多并发,从这个小例子也可以看出goroutine的强大之处。
|
||||
@@ -253,43 +253,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
有的朋友可能要问:这个服务端没有处理客户端实际请求的内容。如果我们需要通过从客户端发送不同的请求来获取不同的时间格式,而且需要一个长连接,该怎么做呢?请看:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := ":1200"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go handleClient(conn)
|
||||
func main() {
|
||||
service := ":1200"
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||
checkError(err)
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleClient(conn net.Conn) {
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout
|
||||
request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack
|
||||
defer conn.Close() // close connection before exit
|
||||
for {
|
||||
read_len, err := conn.Read(request)
|
||||
func handleClient(conn net.Conn) {
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout
|
||||
request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack
|
||||
defer conn.Close() // close connection before exit
|
||||
for {
|
||||
read_len, err := conn.Read(request)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
if read_len == 0 {
|
||||
break // connection already closed by client
|
||||
@@ -302,15 +302,15 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
}
|
||||
|
||||
request = make([]byte, 128) // clear last read content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
在上面这个例子中,我们使用`conn.Read()`不断读取客户端发来的请求。由于我们需要保持与客户端的长连接,所以不能在读取完一次请求后就关闭连接。由于`conn.SetReadDeadline()`设置了超时,当一定时间内客户端无请求发送,`conn`便会自动关闭,下面的for循环即会因为连接已关闭而跳出。需要注意的是,`request`在创建时需要指定一个最大长度以防止flood attack;每次读取到请求处理完毕后,需要清理request,因为`conn.Read()`会将新读取到的内容append到原内容之后。
|
||||
@@ -319,20 +319,20 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回
|
||||
TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数:
|
||||
```Go
|
||||
|
||||
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
|
||||
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
|
||||
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
|
||||
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
|
||||
```
|
||||
设置keepAlive属性,是操作系统层在tcp上没有数据和ACK的时候,会间隔性的发送keepalive包,操作系统可以通过该包来判断一个tcp连接是否已经断开,在windows上默认2个小时没有收到数据和keepalive包的时候人为tcp连接已经断开,这个功能和我们通常在应用层加的心跳包的功能类似。
|
||||
|
||||
@@ -341,86 +341,86 @@ TCP有很多连接控制函数,我们平常用到比较多的有如下几个
|
||||
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)
|
||||
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
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.DialUDP("udp", nil, udpAddr)
|
||||
checkError(err)
|
||||
_, err = conn.Write([]byte("anything"))
|
||||
checkError(err)
|
||||
var buf [512]byte
|
||||
n, err := conn.Read(buf[0:])
|
||||
checkError(err)
|
||||
fmt.Println(string(buf[0:n]))
|
||||
os.Exit(0)
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.DialUDP("udp", nil, udpAddr)
|
||||
checkError(err)
|
||||
_, err = conn.Write([]byte("anything"))
|
||||
checkError(err)
|
||||
var buf [512]byte
|
||||
n, err := conn.Read(buf[0:])
|
||||
checkError(err)
|
||||
fmt.Println(string(buf[0:n]))
|
||||
os.Exit(0)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
我们来看一下UDP服务器端如何来处理:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := ":1200"
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.ListenUDP("udp", udpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
handleClient(conn)
|
||||
}
|
||||
func main() {
|
||||
service := ":1200"
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||
checkError(err)
|
||||
conn, err := net.ListenUDP("udp", udpAddr)
|
||||
checkError(err)
|
||||
for {
|
||||
handleClient(conn)
|
||||
}
|
||||
func handleClient(conn *net.UDPConn) {
|
||||
var buf [512]byte
|
||||
_, addr, err := conn.ReadFromUDP(buf[0:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
daytime := time.Now().String()
|
||||
conn.WriteToUDP([]byte(daytime), addr)
|
||||
}
|
||||
func handleClient(conn *net.UDPConn) {
|
||||
var buf [512]byte
|
||||
_, addr, err := conn.ReadFromUDP(buf[0:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
daytime := time.Now().String()
|
||||
conn.WriteToUDP([]byte(daytime), addr)
|
||||
}
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 总结
|
||||
|
||||
120
zh/08.2.md
120
zh/08.2.md
@@ -47,46 +47,46 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的
|
||||
|
||||
```html
|
||||
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var sock = null;
|
||||
var wsuri = "ws://127.0.0.1:1234";
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var sock = null;
|
||||
var wsuri = "ws://127.0.0.1:1234";
|
||||
|
||||
window.onload = function() {
|
||||
window.onload = function() {
|
||||
|
||||
console.log("onload");
|
||||
console.log("onload");
|
||||
|
||||
sock = new WebSocket(wsuri);
|
||||
sock = new WebSocket(wsuri);
|
||||
|
||||
sock.onopen = function() {
|
||||
console.log("connected to " + wsuri);
|
||||
}
|
||||
sock.onopen = function() {
|
||||
console.log("connected to " + wsuri);
|
||||
}
|
||||
|
||||
sock.onclose = function(e) {
|
||||
console.log("connection closed (" + e.code + ")");
|
||||
}
|
||||
sock.onclose = function(e) {
|
||||
console.log("connection closed (" + e.code + ")");
|
||||
}
|
||||
|
||||
sock.onmessage = function(e) {
|
||||
console.log("message received: " + e.data);
|
||||
}
|
||||
};
|
||||
sock.onmessage = function(e) {
|
||||
console.log("message received: " + e.data);
|
||||
}
|
||||
};
|
||||
|
||||
function send() {
|
||||
var msg = document.getElementById('message').value;
|
||||
sock.send(msg);
|
||||
};
|
||||
</script>
|
||||
<h1>WebSocket Echo Test</h1>
|
||||
<form>
|
||||
<p>
|
||||
Message: <input id="message" type="text" value="Hello, world!">
|
||||
</p>
|
||||
</form>
|
||||
<button onclick="send();">Send Message</button>
|
||||
</body>
|
||||
</html>
|
||||
function send() {
|
||||
var msg = document.getElementById('message').value;
|
||||
sock.send(msg);
|
||||
};
|
||||
</script>
|
||||
<h1>WebSocket Echo Test</h1>
|
||||
<form>
|
||||
<p>
|
||||
Message: <input id="message" type="text" value="Hello, world!">
|
||||
</p>
|
||||
</form>
|
||||
<button onclick="send();">Send Message</button>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
可以看到客户端JS,很容易的就通过WebSocket函数建立了一个与服务器的连接sock,当握手成功后,会触发WebScoket对象的onopen事件,告诉客户端连接已经成功建立。客户端一共绑定了四个事件。
|
||||
@@ -100,45 +100,45 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/net/websocket"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
import (
|
||||
"golang.org/x/net/websocket"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Echo(ws *websocket.Conn) {
|
||||
var err error
|
||||
func Echo(ws *websocket.Conn) {
|
||||
var err error
|
||||
|
||||
for {
|
||||
var reply string
|
||||
for {
|
||||
var reply string
|
||||
|
||||
if err = websocket.Message.Receive(ws, &reply); err != nil {
|
||||
fmt.Println("Can't receive")
|
||||
break
|
||||
}
|
||||
if err = websocket.Message.Receive(ws, &reply); err != nil {
|
||||
fmt.Println("Can't receive")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println("Received back from client: " + reply)
|
||||
fmt.Println("Received back from client: " + reply)
|
||||
|
||||
msg := "Received: " + reply
|
||||
fmt.Println("Sending to client: " + msg)
|
||||
msg := "Received: " + reply
|
||||
fmt.Println("Sending to client: " + msg)
|
||||
|
||||
if err = websocket.Message.Send(ws, msg); err != nil {
|
||||
fmt.Println("Can't send")
|
||||
break
|
||||
}
|
||||
if err = websocket.Message.Send(ws, msg); err != nil {
|
||||
fmt.Println("Can't send")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.Handle("/", websocket.Handler(Echo))
|
||||
func main() {
|
||||
http.Handle("/", websocket.Handler(Echo))
|
||||
|
||||
if err := http.ListenAndServe(":1234", nil); err != nil {
|
||||
log.Fatal("ListenAndServe:", err)
|
||||
}
|
||||
if err := http.ListenAndServe(":1234", nil); err != nil {
|
||||
log.Fatal("ListenAndServe:", err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
当客户端将用户输入的信息Send之后,服务器端通过Receive接收到了相应信息,然后通过Send发送了应答信息。
|
||||
|
||||
80
zh/08.3.md
80
zh/08.3.md
@@ -64,56 +64,56 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
fmt.Fprint(w, "Welcome!\n")
|
||||
}
|
||||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
fmt.Fprint(w, "Welcome!\n")
|
||||
}
|
||||
|
||||
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||
}
|
||||
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
||||
}
|
||||
|
||||
func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are get user %s", uid)
|
||||
}
|
||||
func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are get user %s", uid)
|
||||
}
|
||||
|
||||
func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are modify user %s", uid)
|
||||
}
|
||||
func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are modify user %s", uid)
|
||||
}
|
||||
|
||||
func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are delete user %s", uid)
|
||||
}
|
||||
func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are delete user %s", uid)
|
||||
}
|
||||
|
||||
func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
// uid := r.FormValue("uid")
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are add user %s", uid)
|
||||
}
|
||||
func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
// uid := r.FormValue("uid")
|
||||
uid := ps.ByName("uid")
|
||||
fmt.Fprintf(w, "you are add user %s", uid)
|
||||
}
|
||||
|
||||
func main() {
|
||||
router := httprouter.New()
|
||||
router.GET("/", Index)
|
||||
router.GET("/hello/:name", Hello)
|
||||
func main() {
|
||||
router := httprouter.New()
|
||||
router.GET("/", Index)
|
||||
router.GET("/hello/:name", Hello)
|
||||
|
||||
router.GET("/user/:uid", getuser)
|
||||
router.POST("/adduser/:uid", adduser)
|
||||
router.DELETE("/deluser/:uid", deleteuser)
|
||||
router.PUT("/moduser/:uid", modifyuser)
|
||||
router.GET("/user/:uid", getuser)
|
||||
router.POST("/adduser/:uid", adduser)
|
||||
router.DELETE("/deluser/:uid", deleteuser)
|
||||
router.PUT("/moduser/:uid", modifyuser)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", router))
|
||||
}
|
||||
log.Fatal(http.ListenAndServe(":8080", router))
|
||||
}
|
||||
|
||||
```
|
||||
上面的代码演示了如何编写一个REST的应用,我们访问的资源是用户,我们通过不同的method来访问不同的函数,这里使用了第三方库`github.com/julienschmidt/httprouter`,在前面章节我们介绍过如何实现自定义的路由器,这个库实现了自定义路由和方便的路由规则映射,通过它,我们可以很方便的实现REST的架构。通过上面的代码可知,REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。
|
||||
|
||||
522
zh/08.4.md
522
zh/08.4.md
@@ -47,50 +47,50 @@ http的服务端代码实现如下:
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
)
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
type Arith int
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
type Arith int
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
rpc.HandleHTTP()
|
||||
|
||||
err := http.ListenAndServe(":1234", nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
func main() {
|
||||
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
rpc.HandleHTTP()
|
||||
|
||||
err := http.ListenAndServe(":1234", nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
通过上面的例子可以看到,我们注册了一个Arith的RPC服务,然后通过`rpc.HandleHTTP`函数把该服务注册到了HTTP协议上,然后我们就可以利用http的方式来传递数据了。
|
||||
@@ -99,59 +99,59 @@ http的服务端代码实现如下:
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server")
|
||||
os.Exit(1)
|
||||
}
|
||||
serverAddress := os.Args[1]
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server")
|
||||
os.Exit(1)
|
||||
}
|
||||
serverAddress := os.Args[1]
|
||||
|
||||
client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
|
||||
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
我们把上面的服务端和客户端的代码分别编译,然后先把服务端开启,然后开启客户端,输入代码,就会输出如下信息:
|
||||
```Go
|
||||
|
||||
$ ./http_c localhost
|
||||
Arith: 17*8=136
|
||||
Arith: 17/8=2 remainder 1
|
||||
$ ./http_c localhost
|
||||
Arith: 17*8=136
|
||||
Arith: 17/8=2 remainder 1
|
||||
|
||||
```
|
||||
通过上面的调用可以看到参数和返回值是我们定义的struct类型,在服务端我们把它们当做调用函数的参数的类型,在客户端作为`client.Call`的第2,3两个参数的类型。客户端最重要的就是这个Call函数,它有3个参数,第1个要调用的函数的名字,第2个是要传递的参数,第3个要返回的参数(注意是指针类型),通过上面的代码例子我们可以发现,使用Go的RPC实现相当的简单,方便。
|
||||
@@ -159,68 +159,68 @@ http的服务端代码实现如下:
|
||||
上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
type Arith int
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
func main() {
|
||||
|
||||
type Arith int
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
|
||||
checkError(err)
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
|
||||
func main() {
|
||||
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
|
||||
checkError(err)
|
||||
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rpc.ServeConn(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
continue
|
||||
}
|
||||
rpc.ServeConn(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。
|
||||
|
||||
@@ -229,51 +229,51 @@ http的服务端代码实现如下:
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server:port")
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
client, err := rpc.Dial("tcp", service)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server:port")
|
||||
os.Exit(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
|
||||
client, err := rpc.Dial("tcp", service)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
|
||||
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。
|
||||
@@ -283,121 +283,121 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
type Arith int
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
func main() {
|
||||
|
||||
type Arith int
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
|
||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
||||
*reply = args.A * args.B
|
||||
return nil
|
||||
}
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
|
||||
checkError(err)
|
||||
|
||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
||||
if args.B == 0 {
|
||||
return errors.New("divide by zero")
|
||||
}
|
||||
quo.Quo = args.A / args.B
|
||||
quo.Rem = args.A % args.B
|
||||
return nil
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
|
||||
func main() {
|
||||
|
||||
arith := new(Arith)
|
||||
rpc.Register(arith)
|
||||
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
|
||||
checkError(err)
|
||||
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
checkError(err)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
jsonrpc.ServeConn(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
continue
|
||||
}
|
||||
jsonrpc.ServeConn(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
通过示例我们可以看出 json-rpc是基于TCP协议实现的,目前它还不支持HTTP方式。
|
||||
|
||||
请看客户端的实现代码:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc/jsonrpc"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/rpc/jsonrpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
A, B int
|
||||
type Args struct {
|
||||
A, B int
|
||||
}
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server:port")
|
||||
log.Fatal(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
|
||||
type Quotient struct {
|
||||
Quo, Rem int
|
||||
client, err := jsonrpc.Dial("tcp", service)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: ", os.Args[0], "server:port")
|
||||
log.Fatal(1)
|
||||
}
|
||||
service := os.Args[1]
|
||||
|
||||
client, err := jsonrpc.Dial("tcp", service)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
|
||||
|
||||
// Synchronous call
|
||||
args := Args{17, 8}
|
||||
var reply int
|
||||
err = client.Call("Arith.Multiply", args, &reply)
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
||||
|
||||
var quot Quotient
|
||||
err = client.Call("Arith.Divide", args, ")
|
||||
if err != nil {
|
||||
log.Fatal("arith error:", err)
|
||||
}
|
||||
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的支持,欣慰的是现在已经有第三方的开源实现了。
|
||||
|
||||
34
zh/09.1.md
34
zh/09.1.md
@@ -51,8 +51,8 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从
|
||||
|
||||
```Go
|
||||
|
||||
mux.Get("/user/:uid", getuser)
|
||||
mux.Post("/user/:uid", modifyuser)
|
||||
mux.Get("/user/:uid", getuser)
|
||||
mux.Post("/user/:uid", modifyuser)
|
||||
|
||||
```
|
||||
这样处理后,因为我们限定了修改只能使用POST,当GET方式请求时就拒绝响应,所以上面图示中GET方式的CSRF攻击就可以防止了,但这样就能全部解决问题了吗?当然不是,因为POST也是可以模拟的。
|
||||
@@ -67,33 +67,33 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从
|
||||
|
||||
```Go
|
||||
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
io.WriteString(h, "ganraomaxxxxxxxxx")
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
io.WriteString(h, "ganraomaxxxxxxxxx")
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
|
||||
```
|
||||
输出token
|
||||
```html
|
||||
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
|
||||
```
|
||||
验证token
|
||||
|
||||
```Go
|
||||
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
//验证token的合法性
|
||||
} else {
|
||||
//不存在token报错
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
//验证token的合法性
|
||||
} else {
|
||||
//不存在token报错
|
||||
}
|
||||
|
||||
```
|
||||
这样基本就实现了安全的POST,但是也许你会说如果破解了token的算法呢,按照理论上是,但是实际上破解是基本不可能的,因为有人曾计算过,暴力破解该串大概需要2的11次方时间。
|
||||
|
||||
|
||||
44
zh/09.2.md
44
zh/09.2.md
@@ -33,26 +33,26 @@
|
||||
接下来,让我们通过一个例子来巩固这些概念,请看下面这个表单
|
||||
```html
|
||||
|
||||
<form action="/whoami" method="POST">
|
||||
我是谁:
|
||||
<select name="name">
|
||||
<option value="astaxie">astaxie</option>
|
||||
<option value="herry">herry</option>
|
||||
<option value="marry">marry</option>
|
||||
</select>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<form action="/whoami" method="POST">
|
||||
我是谁:
|
||||
<select name="name">
|
||||
<option value="astaxie">astaxie</option>
|
||||
<option value="herry">herry</option>
|
||||
<option value="marry">marry</option>
|
||||
</select>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
||||
```
|
||||
在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟POST操作,递交`name=attack`这样的数据,所以在此时我们需要做类似白名单的处理
|
||||
```Go
|
||||
|
||||
r.ParseForm()
|
||||
name := r.Form.Get("name")
|
||||
CleanMap := make(map[string]interface{}, 0)
|
||||
if name == "astaxie" || name == "herry" || name == "marry" {
|
||||
CleanMap["name"] = name
|
||||
}
|
||||
r.ParseForm()
|
||||
name := r.Form.Get("name")
|
||||
CleanMap := make(map[string]interface{}, 0)
|
||||
if name == "astaxie" || name == "herry" || name == "marry" {
|
||||
CleanMap["name"] = name
|
||||
}
|
||||
|
||||
```
|
||||
上面代码中我们初始化了一个CleanMap的变量,当判断获取的name是`astaxie`、`herry`、`marry`三个中的一个之后
|
||||
@@ -61,13 +61,13 @@
|
||||
上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成:
|
||||
```Go
|
||||
|
||||
r.ParseForm()
|
||||
username := r.Form.Get("username")
|
||||
CleanMap := make(map[string]interface{}, 0)
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok {
|
||||
CleanMap["username"] = username
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
username := r.Form.Get("username")
|
||||
CleanMap := make(map[string]interface{}, 0)
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok {
|
||||
CleanMap["username"] = username
|
||||
}
|
||||
|
||||
```
|
||||
## 总结
|
||||
数据过滤在Web安全中起到一个基石的作用,大多数的安全问题都是由于没有过滤数据和验证数据引起的,例如前面小节的CSRF攻击,以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的,因此我们需要特别重视这部分的内容。
|
||||
|
||||
@@ -40,9 +40,9 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用
|
||||
|
||||
```Go
|
||||
|
||||
`w.Header().Set("Content-Type","text/javascript")`
|
||||
`w.Header().Set("Content-Type","text/javascript")`
|
||||
|
||||
这样就可以让浏览器解析javascript代码,而不会是html输出。
|
||||
这样就可以让浏览器解析javascript代码,而不会是html输出。
|
||||
|
||||
```
|
||||
## 总结
|
||||
|
||||
26
zh/09.4.md
26
zh/09.4.md
@@ -11,44 +11,44 @@ SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常
|
||||
考虑以下简单的登录表单:
|
||||
```html
|
||||
|
||||
<form action="/login" method="POST">
|
||||
<p>Username: <input type="text" name="username" /></p>
|
||||
<p>Password: <input type="password" name="password" /></p>
|
||||
<p><input type="submit" value="登陆" /></p>
|
||||
</form>
|
||||
<form action="/login" method="POST">
|
||||
<p>Username: <input type="text" name="username" /></p>
|
||||
<p>Password: <input type="password" name="password" /></p>
|
||||
<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+"'"
|
||||
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' --
|
||||
myuser' or 'foo' = 'foo' --
|
||||
|
||||
```
|
||||
那么我们的SQL变成了如下所示:
|
||||
```Go
|
||||
|
||||
SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx'
|
||||
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)
|
||||
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'--%'"
|
||||
sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"
|
||||
```
|
||||
MSSQL服务器会执行这条SQL语句,包括它后面那个用于向系统添加新用户的命令。如果这个程序是以sa运行而 MSSQLSERVER服务又有足够的权限的话,攻击者就可以获得一个系统帐号来访问主机了。
|
||||
|
||||
|
||||
56
zh/09.5.md
56
zh/09.5.md
@@ -9,20 +9,20 @@
|
||||
Go语言对这三种加密算法的实现如下所示:
|
||||
```Go
|
||||
|
||||
//import "crypto/sha256"
|
||||
h := sha256.New()
|
||||
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
|
||||
fmt.Printf("% x", h.Sum(nil))
|
||||
//import "crypto/sha256"
|
||||
h := sha256.New()
|
||||
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
|
||||
fmt.Printf("% x", h.Sum(nil))
|
||||
|
||||
//import "crypto/sha1"
|
||||
h := sha1.New()
|
||||
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
|
||||
fmt.Printf("% x", h.Sum(nil))
|
||||
//import "crypto/sha1"
|
||||
h := sha1.New()
|
||||
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
|
||||
fmt.Printf("% x", h.Sum(nil))
|
||||
|
||||
//import "crypto/md5"
|
||||
h := md5.New()
|
||||
io.WriteString(h, "需要加密的密码")
|
||||
fmt.Printf("%x", h.Sum(nil))
|
||||
//import "crypto/md5"
|
||||
h := md5.New()
|
||||
io.WriteString(h, "需要加密的密码")
|
||||
fmt.Printf("%x", h.Sum(nil))
|
||||
|
||||
```
|
||||
单向哈希有两个特性:
|
||||
@@ -44,25 +44,25 @@ Go语言对这三种加密算法的实现如下所示:
|
||||
|
||||
```Go
|
||||
|
||||
//import "crypto/md5"
|
||||
//假设用户名abc,密码123456
|
||||
h := md5.New()
|
||||
io.WriteString(h, "需要加密的密码")
|
||||
//import "crypto/md5"
|
||||
//假设用户名abc,密码123456
|
||||
h := md5.New()
|
||||
io.WriteString(h, "需要加密的密码")
|
||||
|
||||
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
|
||||
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
|
||||
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
|
||||
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
//指定两个 salt: salt1 = @#$% salt2 = ^&*()
|
||||
salt1 := "@#$%"
|
||||
salt2 := "^&*()"
|
||||
//指定两个 salt: salt1 = @#$% salt2 = ^&*()
|
||||
salt1 := "@#$%"
|
||||
salt2 := "^&*()"
|
||||
|
||||
//salt1+用户名+salt2+MD5拼接
|
||||
io.WriteString(h, salt1)
|
||||
io.WriteString(h, "abc")
|
||||
io.WriteString(h, salt2)
|
||||
io.WriteString(h, pwmd5)
|
||||
//salt1+用户名+salt2+MD5拼接
|
||||
io.WriteString(h, salt1)
|
||||
io.WriteString(h, "abc")
|
||||
io.WriteString(h, salt2)
|
||||
io.WriteString(h, pwmd5)
|
||||
|
||||
last :=fmt.Sprintf("%x", h.Sum(nil))
|
||||
last :=fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
```
|
||||
在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。
|
||||
@@ -79,7 +79,7 @@ Go语言对这三种加密算法的实现如下所示:
|
||||
目前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)
|
||||
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
|
||||
```
|
||||
通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。
|
||||
|
||||
|
||||
156
zh/09.6.md
156
zh/09.6.md
@@ -6,38 +6,38 @@
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func base64Encode(src []byte) []byte {
|
||||
return []byte(base64.StdEncoding.EncodeToString(src))
|
||||
func base64Encode(src []byte) []byte {
|
||||
return []byte(base64.StdEncoding.EncodeToString(src))
|
||||
}
|
||||
|
||||
func base64Decode(src []byte) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(string(src))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// encode
|
||||
hello := "你好,世界! hello world"
|
||||
debyte := base64Encode([]byte(hello))
|
||||
fmt.Println(debyte)
|
||||
// decode
|
||||
enbyte, err := base64Decode(debyte)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
func base64Decode(src []byte) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(string(src))
|
||||
if hello != string(enbyte) {
|
||||
fmt.Println("hello is not equal to enbyte")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// encode
|
||||
hello := "你好,世界! hello world"
|
||||
debyte := base64Encode([]byte(hello))
|
||||
fmt.Println(debyte)
|
||||
// decode
|
||||
enbyte, err := base64Decode(debyte)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
if hello != string(enbyte) {
|
||||
fmt.Println("hello is not equal to enbyte")
|
||||
}
|
||||
|
||||
fmt.Println(string(enbyte))
|
||||
}
|
||||
fmt.Println(string(enbyte))
|
||||
}
|
||||
|
||||
```
|
||||
## 高级加解密
|
||||
@@ -50,70 +50,70 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有:
|
||||
因为这两种算法使用方法类似,所以在此,我们仅用aes包为例来讲解它们的使用,请看下面的例子
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
|
||||
var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
|
||||
|
||||
func main() {
|
||||
//需要去加密的字符串
|
||||
plaintext := []byte("My name is Astaxie")
|
||||
//如果传入加密串的话,plaint就是传入的字符串
|
||||
if len(os.Args) > 1 {
|
||||
plaintext = []byte(os.Args[1])
|
||||
}
|
||||
|
||||
//aes的加密字符串
|
||||
key_text := "astaxie12798akljzmknm.ahkjkljl;k"
|
||||
if len(os.Args) > 2 {
|
||||
key_text = os.Args[2]
|
||||
}
|
||||
|
||||
fmt.Println(len(key_text))
|
||||
|
||||
// 创建加密算法aes
|
||||
c, err := aes.NewCipher([]byte(key_text))
|
||||
if err != nil {
|
||||
fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
//加密字符串
|
||||
cfb := cipher.NewCFBEncrypter(c, commonIV)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
cfb.XORKeyStream(ciphertext, plaintext)
|
||||
fmt.Printf("%s=>%x\n", plaintext, ciphertext)
|
||||
|
||||
// 解密字符串
|
||||
cfbdec := cipher.NewCFBDecrypter(c, commonIV)
|
||||
plaintextCopy := make([]byte, len(plaintext))
|
||||
cfbdec.XORKeyStream(plaintextCopy, ciphertext)
|
||||
fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy)
|
||||
func main() {
|
||||
//需要去加密的字符串
|
||||
plaintext := []byte("My name is Astaxie")
|
||||
//如果传入加密串的话,plaint就是传入的字符串
|
||||
if len(os.Args) > 1 {
|
||||
plaintext = []byte(os.Args[1])
|
||||
}
|
||||
|
||||
//aes的加密字符串
|
||||
key_text := "astaxie12798akljzmknm.ahkjkljl;k"
|
||||
if len(os.Args) > 2 {
|
||||
key_text = os.Args[2]
|
||||
}
|
||||
|
||||
fmt.Println(len(key_text))
|
||||
|
||||
// 创建加密算法aes
|
||||
c, err := aes.NewCipher([]byte(key_text))
|
||||
if err != nil {
|
||||
fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
//加密字符串
|
||||
cfb := cipher.NewCFBEncrypter(c, commonIV)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
cfb.XORKeyStream(ciphertext, plaintext)
|
||||
fmt.Printf("%s=>%x\n", plaintext, ciphertext)
|
||||
|
||||
// 解密字符串
|
||||
cfbdec := cipher.NewCFBDecrypter(c, commonIV)
|
||||
plaintextCopy := make([]byte, len(plaintext))
|
||||
cfbdec.XORKeyStream(plaintextCopy, ciphertext)
|
||||
fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy)
|
||||
}
|
||||
|
||||
```
|
||||
上面通过调用函数`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
|
||||
type Block interface {
|
||||
// BlockSize returns the cipher's block size.
|
||||
BlockSize() int
|
||||
|
||||
// Encrypt encrypts the first block in src into dst.
|
||||
// Dst and src may point at the same memory.
|
||||
Encrypt(dst, src []byte)
|
||||
// Encrypt encrypts the first block in src into dst.
|
||||
// Dst and src may point at the same memory.
|
||||
Encrypt(dst, src []byte)
|
||||
|
||||
// Decrypt decrypts the first block in src into dst.
|
||||
// Dst and src may point at the same memory.
|
||||
Decrypt(dst, src []byte)
|
||||
}
|
||||
// Decrypt decrypts the first block in src into dst.
|
||||
// Dst and src may point at the same memory.
|
||||
Decrypt(dst, src []byte)
|
||||
}
|
||||
```
|
||||
这三个函数实现了加解密操作,详细的操作请看上面的例子。
|
||||
|
||||
|
||||
48
zh/10.1.md
48
zh/10.1.md
@@ -21,26 +21,26 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
我们可以通过下面的代码来实现域名的对应locale:
|
||||
```Go
|
||||
|
||||
if r.Host == "www.asta.com" {
|
||||
i18n.SetLocale("en")
|
||||
} else if r.Host == "www.asta.cn" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if r.Host == "www.asta.tw" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
if r.Host == "www.asta.com" {
|
||||
i18n.SetLocale("en")
|
||||
} else if r.Host == "www.asta.cn" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if r.Host == "www.asta.tw" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
```
|
||||
当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示:
|
||||
```Go
|
||||
|
||||
prefix := strings.Split(r.Host,".")
|
||||
prefix := strings.Split(r.Host,".")
|
||||
|
||||
if prefix[0] == "en" {
|
||||
i18n.SetLocale("en")
|
||||
} else if prefix[0] == "cn" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if prefix[0] == "tw" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
if prefix[0] == "en" {
|
||||
i18n.SetLocale("en")
|
||||
} else if prefix[0] == "cn" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if prefix[0] == "tw" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
```
|
||||
通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。
|
||||
|
||||
@@ -52,7 +52,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
也许我们希望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)
|
||||
mux.Get("/:locale/books", listbook)
|
||||
```
|
||||
### 从客户端设置地区
|
||||
在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。
|
||||
@@ -62,14 +62,14 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三
|
||||
客户端请求的时候在HTTP头信息里面有`Accept-Language`,一般的客户端都会设置该信息,下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码:
|
||||
```Go
|
||||
|
||||
AL := r.Header.Get("Accept-Language")
|
||||
if AL == "en" {
|
||||
i18n.SetLocale("en")
|
||||
} else if AL == "zh-CN" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if AL == "zh-TW" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
AL := r.Header.Get("Accept-Language")
|
||||
if AL == "en" {
|
||||
i18n.SetLocale("en")
|
||||
} else if AL == "zh-CN" {
|
||||
i18n.SetLocale("zh-CN")
|
||||
} else if AL == "zh-TW" {
|
||||
i18n.SetLocale("zh-TW")
|
||||
}
|
||||
```
|
||||
当然在实际应用中,可能需要更加严格的判断来进行设置地区
|
||||
- IP地址
|
||||
|
||||
142
zh/10.2.md
142
zh/10.2.md
@@ -6,35 +6,35 @@
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "fmt"
|
||||
|
||||
var locales map[string]map[string]string
|
||||
var locales map[string]map[string]string
|
||||
|
||||
func main() {
|
||||
locales = make(map[string]map[string]string, 2)
|
||||
en := make(map[string]string, 10)
|
||||
en["pea"] = "pea"
|
||||
en["bean"] = "bean"
|
||||
locales["en"] = en
|
||||
cn := make(map[string]string, 10)
|
||||
cn["pea"] = "豌豆"
|
||||
cn["bean"] = "毛豆"
|
||||
locales["zh-CN"] = cn
|
||||
lang := "zh-CN"
|
||||
fmt.Println(msg(lang, "pea"))
|
||||
fmt.Println(msg(lang, "bean"))
|
||||
}
|
||||
func main() {
|
||||
locales = make(map[string]map[string]string, 2)
|
||||
en := make(map[string]string, 10)
|
||||
en["pea"] = "pea"
|
||||
en["bean"] = "bean"
|
||||
locales["en"] = en
|
||||
cn := make(map[string]string, 10)
|
||||
cn["pea"] = "豌豆"
|
||||
cn["bean"] = "毛豆"
|
||||
locales["zh-CN"] = cn
|
||||
lang := "zh-CN"
|
||||
fmt.Println(msg(lang, "pea"))
|
||||
fmt.Println(msg(lang, "bean"))
|
||||
}
|
||||
|
||||
func msg(locale, key string) string {
|
||||
if v, ok := locales[locale]; ok {
|
||||
if v2, ok := v[key]; ok {
|
||||
return v2
|
||||
}
|
||||
func msg(locale, key string) string {
|
||||
if v, ok := locales[locale]; ok {
|
||||
if v2, ok := v[key]; ok {
|
||||
return v2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
```
|
||||
上面示例演示了不同locale的文本翻译,实现了中文和英文对于同一个key显示不同语言的实现,上面实现了中文的文本消息,如果想切换到英文版本,只需要把lang设置为en即可。
|
||||
@@ -42,10 +42,10 @@
|
||||
有些时候仅是key-value替换是不能满足需要的,例如"I am 30 years old",中文表达是"我今年30岁了",而此处的30是一个变量,该怎么办呢?这个时候,我们可以结合`fmt.Printf`函数来实现,请看下面的代码:
|
||||
```Go
|
||||
|
||||
en["how old"] ="I am %d years old"
|
||||
cn["how old"] ="我今年%d岁了"
|
||||
en["how old"] ="I am %d years old"
|
||||
cn["how old"] ="我今年%d岁了"
|
||||
|
||||
fmt.Printf(msg(lang, "how old"), 30)
|
||||
fmt.Printf(msg(lang, "how old"), 30)
|
||||
```
|
||||
上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。
|
||||
|
||||
@@ -59,82 +59,82 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为
|
||||
|
||||
```Go
|
||||
|
||||
en["time_zone"]="America/Chicago"
|
||||
cn["time_zone"]="Asia/Shanghai"
|
||||
en["time_zone"]="America/Chicago"
|
||||
cn["time_zone"]="Asia/Shanghai"
|
||||
|
||||
loc,_:=time.LoadLocation(msg(lang,"time_zone"))
|
||||
t:=time.Now()
|
||||
t = t.In(loc)
|
||||
fmt.Println(t.Format(time.RFC3339))
|
||||
loc,_:=time.LoadLocation(msg(lang,"time_zone"))
|
||||
t:=time.Now()
|
||||
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秒"
|
||||
en["date_format"]="%Y-%m-%d %H:%M:%S"
|
||||
cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒"
|
||||
|
||||
fmt.Println(date(msg(lang,"date_format"),t))
|
||||
fmt.Println(date(msg(lang,"date_format"),t))
|
||||
|
||||
func date(fomate string,t time.Time) string{
|
||||
year, month, day = t.Date()
|
||||
hour, min, sec = t.Clock()
|
||||
//解析相应的%Y %m %d %H %M %S然后返回信息
|
||||
//%Y 替换成2012
|
||||
//%m 替换成10
|
||||
//%d 替换成24
|
||||
}
|
||||
func date(fomate string,t time.Time) string{
|
||||
year, month, day = t.Date()
|
||||
hour, min, sec = t.Clock()
|
||||
//解析相应的%Y %m %d %H %M %S然后返回信息
|
||||
//%Y 替换成2012
|
||||
//%m 替换成10
|
||||
//%d 替换成24
|
||||
}
|
||||
|
||||
```
|
||||
## 本地化货币值
|
||||
各个地区的货币表示也不一样,处理方式也与日期差不多,细节请看下面代码:
|
||||
```Go
|
||||
|
||||
en["money"] ="USD %d"
|
||||
cn["money"] ="¥%d元"
|
||||
en["money"] ="USD %d"
|
||||
cn["money"] ="¥%d元"
|
||||
|
||||
fmt.Println(date(msg(lang,"date_format"),100))
|
||||
fmt.Println(date(msg(lang,"date_format"),100))
|
||||
|
||||
func money_format(fomate string,money int64) string{
|
||||
return fmt.Sprintf(fomate,money)
|
||||
}
|
||||
func money_format(fomate string,money int64) string{
|
||||
return fmt.Sprintf(fomate,money)
|
||||
}
|
||||
|
||||
```
|
||||
## 本地化视图和资源
|
||||
我们可能会根据Locale的不同来展示视图,这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢?首先我们应按locale来组织文件信息,请看下面的文件目录安排:
|
||||
```html
|
||||
|
||||
views
|
||||
|--en //英文模板
|
||||
|--images //存储图片信息
|
||||
|--js //存储JS文件
|
||||
|--css //存储css文件
|
||||
index.tpl //用户首页
|
||||
login.tpl //登陆首页
|
||||
|--zh-CN //中文模板
|
||||
|--images
|
||||
|--js
|
||||
|--css
|
||||
index.tpl
|
||||
login.tpl
|
||||
views
|
||||
|--en //英文模板
|
||||
|--images //存储图片信息
|
||||
|--js //存储JS文件
|
||||
|--css //存储css文件
|
||||
index.tpl //用户首页
|
||||
login.tpl //登陆首页
|
||||
|--zh-CN //中文模板
|
||||
|--images
|
||||
|--js
|
||||
|--css
|
||||
index.tpl
|
||||
login.tpl
|
||||
|
||||
```
|
||||
有了这个目录结构后我们就可以在渲染的地方这样来实现代码:
|
||||
```Go
|
||||
|
||||
s1, _ := template.ParseFiles("views"+lang+"index.tpl")
|
||||
VV.Lang=lang
|
||||
s1.Execute(os.Stdout, VV)
|
||||
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>
|
||||
// css文件
|
||||
<link href="views/{{.VV.Lang}}/css/bootstrap-responsive.min.css" rel="stylesheet">
|
||||
// 图片文件
|
||||
<img src="views/{{.VV.Lang}}/images/btn.png">
|
||||
// js文件
|
||||
<script type="text/javascript" src="views/{{.VV.Lang}}/js/jquery/jquery-1.8.0.min.js"></script>
|
||||
// css文件
|
||||
<link href="views/{{.VV.Lang}}/css/bootstrap-responsive.min.css" rel="stylesheet">
|
||||
// 图片文件
|
||||
<img src="views/{{.VV.Lang}}/images/btn.png">
|
||||
```
|
||||
采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。
|
||||
|
||||
|
||||
182
zh/10.3.md
182
zh/10.3.md
@@ -5,103 +5,103 @@
|
||||
在开发一个应用的时候,首先我们要决定是只支持一种语言,还是多种语言,如果要支持多种语言,我们则需要制定一个组织结构,以方便将来更多语言的添加。在此我们设计如下:Locale有关的文件放置在config/locales下,假设你要支持中文和英文,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示:
|
||||
```json
|
||||
|
||||
# zh.json
|
||||
# zh.json
|
||||
|
||||
{
|
||||
"zh": {
|
||||
"submit": "提交",
|
||||
"create": "创建"
|
||||
}
|
||||
{
|
||||
"zh": {
|
||||
"submit": "提交",
|
||||
"create": "创建"
|
||||
}
|
||||
}
|
||||
|
||||
#en.json
|
||||
#en.json
|
||||
|
||||
{
|
||||
"en": {
|
||||
"submit": "Submit",
|
||||
"create": "Create"
|
||||
}
|
||||
{
|
||||
"en": {
|
||||
"submit": "Submit",
|
||||
"create": "Create"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
为了支持国际化,在此我们使用了一个国际化相关的包——[go-i18n](https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件
|
||||
```Go
|
||||
|
||||
Tr:=i18n.NewLocale()
|
||||
Tr.LoadPath("config/locales")
|
||||
Tr:=i18n.NewLocale()
|
||||
Tr.LoadPath("config/locales")
|
||||
|
||||
```
|
||||
这个包使用起来很简单,你可以通过下面的方式进行测试:
|
||||
```Go
|
||||
|
||||
fmt.Println(Tr.Translate("submit"))
|
||||
//输出Submit
|
||||
Tr.SetLocale("zh")
|
||||
fmt.Println(Tr.Translate("submit"))
|
||||
//输出“递交”
|
||||
fmt.Println(Tr.Translate("submit"))
|
||||
//输出Submit
|
||||
Tr.SetLocale("zh")
|
||||
fmt.Println(Tr.Translate("submit"))
|
||||
//输出“递交”
|
||||
```
|
||||
## 自动加载本地包
|
||||
上面我们介绍了如何自动加载自定义语言包,其实go-i18n库已经预加载了很多默认的格式信息,例如时间格式、货币格式,用户可以在自定义配置时改写这些默认配置,请看下面的处理过程:
|
||||
```Go
|
||||
|
||||
//加载默认配置文件,这些文件都放在go-i18n/locales下面
|
||||
//加载默认配置文件,这些文件都放在go-i18n/locales下面
|
||||
|
||||
//文件命名zh.json、en-json、en-US.json等,可以不断的扩展支持更多的语言
|
||||
//文件命名zh.json、en-json、en-US.json等,可以不断的扩展支持更多的语言
|
||||
|
||||
func (il *IL) loadDefaultTranslations(dirPath string) error {
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
func (il *IL) loadDefaultTranslations(dirPath string) error {
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
names, err := dir.Readdirnames(-1)
|
||||
names, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
fullPath := path.Join(dirPath, name)
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
fullPath := path.Join(dirPath, name)
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if fi.IsDir() {
|
||||
if err := il.loadTranslations(fullPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if locale := il.matchingLocaleFromFileName(name); locale != "" {
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if fi.IsDir() {
|
||||
if err := il.loadTranslations(fullPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if locale := il.matchingLocaleFromFileName(name); locale != "" {
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := il.loadTranslation(file, locale); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := il.loadTranslation(file, locale); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
```
|
||||
通过上面的方法加载配置信息到默认的文件,这样我们就可以在我们没有自定义时间信息的时候执行如下的代码获取对应的信息:
|
||||
```Go
|
||||
|
||||
//locale=zh的情况下,执行如下代码:
|
||||
//locale=zh的情况下,执行如下代码:
|
||||
|
||||
fmt.Println(Tr.Time(time.Now()))
|
||||
//输出:2009年1月08日 星期四 20:37:58 CST
|
||||
fmt.Println(Tr.Time(time.Now()))
|
||||
//输出:2009年1月08日 星期四 20:37:58 CST
|
||||
|
||||
fmt.Println(Tr.Time(time.Now(),"long"))
|
||||
//输出:2009年1月08日
|
||||
fmt.Println(Tr.Time(time.Now(),"long"))
|
||||
//输出:2009年1月08日
|
||||
|
||||
fmt.Println(Tr.Money(11.11))
|
||||
//输出:¥11.11
|
||||
fmt.Println(Tr.Money(11.11))
|
||||
//输出:¥11.11
|
||||
```
|
||||
## template mapfunc
|
||||
上面我们实现了多个语言包的管理和加载,而一些函数的实现是基于逻辑层的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等,虽然我们在逻辑层可以利用这些函数把需要的参数进行转换后在模板层渲染的时候直接输出,但是如果我们想在模版层直接使用这些函数该怎么实现呢?不知你是否还记得,在前面介绍模板的时候说过:Go语言的模板支持自定义模板函数,下面是我们实现的方便操作的mapfunc:
|
||||
@@ -111,28 +111,28 @@
|
||||
文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下:
|
||||
```Go
|
||||
|
||||
func I18nT(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Translate(s)
|
||||
func I18nT(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Translate(s)
|
||||
}
|
||||
|
||||
```
|
||||
注册函数如下:
|
||||
```Go
|
||||
|
||||
t.Funcs(template.FuncMap{"T": I18nT})
|
||||
t.Funcs(template.FuncMap{"T": I18nT})
|
||||
```
|
||||
模板中使用如下:
|
||||
```Go
|
||||
|
||||
{{.V.Submit | T}}
|
||||
{{.V.Submit | T}}
|
||||
```
|
||||
|
||||
2. 时间日期
|
||||
@@ -140,54 +140,54 @@
|
||||
时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下:
|
||||
```Go
|
||||
|
||||
func I18nTimeDate(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Time(s)
|
||||
func I18nTimeDate(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Time(s)
|
||||
}
|
||||
```
|
||||
注册函数如下:
|
||||
```Go
|
||||
|
||||
t.Funcs(template.FuncMap{"TD": I18nTimeDate})
|
||||
t.Funcs(template.FuncMap{"TD": I18nTimeDate})
|
||||
```
|
||||
模板中使用如下:
|
||||
```Go
|
||||
|
||||
{{.V.Now | TD}}
|
||||
{{.V.Now | TD}}
|
||||
```
|
||||
3. 货币信息
|
||||
|
||||
货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下:
|
||||
```Go
|
||||
|
||||
func I18nMoney(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Money(s)
|
||||
func I18nMoney(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return Tr.Money(s)
|
||||
}
|
||||
```
|
||||
注册函数如下:
|
||||
```Go
|
||||
|
||||
t.Funcs(template.FuncMap{"M": I18nMoney})
|
||||
t.Funcs(template.FuncMap{"M": I18nMoney})
|
||||
```
|
||||
模板中使用如下:
|
||||
```Go
|
||||
|
||||
{{.V.Money | M}}
|
||||
{{.V.Money | M}}
|
||||
```
|
||||
## 总结
|
||||
通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。
|
||||
|
||||
206
zh/11.1.md
206
zh/11.1.md
@@ -2,59 +2,59 @@
|
||||
Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量
|
||||
```Go
|
||||
|
||||
func Open(name string) (file *File, err error)
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
type error interface {
|
||||
Error() string
|
||||
}
|
||||
```
|
||||
error是一个内置的接口类型,我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString
|
||||
```Go
|
||||
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
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}
|
||||
}
|
||||
// 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 {
|
||||
return 0, errors.New("math: square root of negative number")
|
||||
}
|
||||
// implementation
|
||||
func Sqrt(f float64) (float64, error) {
|
||||
if f < 0 {
|
||||
return 0, errors.New("math: square root of negative number")
|
||||
}
|
||||
// implementation
|
||||
}
|
||||
```
|
||||
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
|
||||
```Go
|
||||
|
||||
f, err := Sqrt(-1)
|
||||
f, err := Sqrt(-1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@@ -63,28 +63,28 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相
|
||||
通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例:
|
||||
```Go
|
||||
|
||||
type SyntaxError struct {
|
||||
msg string // 错误描述
|
||||
Offset int64 // 错误发生的位置
|
||||
}
|
||||
type SyntaxError struct {
|
||||
msg string // 错误描述
|
||||
Offset int64 // 错误发生的位置
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
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 {
|
||||
line, col := findLine(f, serr.Offset)
|
||||
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
|
||||
}
|
||||
return err
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
line, col := findLine(f, serr.Offset)
|
||||
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
```
|
||||
需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如:
|
||||
```Go
|
||||
|
||||
func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
|
||||
func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。
|
||||
var err *SyntaxError // 预声明错误变量
|
||||
if 出错条件 {
|
||||
err = &SyntaxError{}
|
||||
@@ -97,25 +97,25 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类
|
||||
上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法:
|
||||
```Go
|
||||
|
||||
package net
|
||||
package net
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}
|
||||
|
||||
```
|
||||
在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试:
|
||||
```Go
|
||||
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
time.Sleep(1e9)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
time.Sleep(1e9)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
## 错误处理
|
||||
Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。
|
||||
@@ -123,91 +123,91 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是
|
||||
请看下面这个例子代码:
|
||||
```Go
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/view", viewRecord)
|
||||
}
|
||||
func init() {
|
||||
http.HandleFunc("/view", viewRecord)
|
||||
}
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。
|
||||
```Go
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) error
|
||||
type appHandler func(http.ResponseWriter, *http.Request) error
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fn(w, r); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fn(w, r); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数:
|
||||
```Go
|
||||
|
||||
func init() {
|
||||
http.Handle("/view", appHandler(viewRecord))
|
||||
}
|
||||
func init() {
|
||||
http.Handle("/view", appHandler(viewRecord))
|
||||
}
|
||||
```
|
||||
当请求/view的时候我们的逻辑处理可以变成如下代码,和第一种实现方式相比较已经简单了很多。
|
||||
```Go
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) error {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return err
|
||||
}
|
||||
return viewTemplate.Execute(w, record)
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) error {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return err
|
||||
}
|
||||
return viewTemplate.Execute(w, record)
|
||||
}
|
||||
```
|
||||
上面的例子错误处理的时候所有的错误返回给用户的都是500错误码,然后打印出来相应的错误代码,其实我们可以把这个错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型:
|
||||
```Go
|
||||
|
||||
type appError struct {
|
||||
Error error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
type appError struct {
|
||||
Error error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
```
|
||||
这样我们的自定义路由器可以改成如下方式:
|
||||
```Go
|
||||
|
||||
type appHandler func(http.ResponseWriter, *http.Request) *appError
|
||||
type appHandler func(http.ResponseWriter, *http.Request) *appError
|
||||
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
|
||||
c := appengine.NewContext(r)
|
||||
c.Errorf("%v", e.Error)
|
||||
http.Error(w, e.Message, e.Code)
|
||||
}
|
||||
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
|
||||
c := appengine.NewContext(r)
|
||||
c.Errorf("%v", e.Error)
|
||||
http.Error(w, e.Message, e.Code)
|
||||
}
|
||||
}
|
||||
```
|
||||
这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:
|
||||
```Go
|
||||
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return &appError{err, "Record not found", 404}
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
return &appError{err, "Can't display record", 500}
|
||||
}
|
||||
return nil
|
||||
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
|
||||
c := appengine.NewContext(r)
|
||||
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
|
||||
record := new(Record)
|
||||
if err := datastore.Get(c, key, record); err != nil {
|
||||
return &appError{err, "Record not found", 404}
|
||||
}
|
||||
if err := viewTemplate.Execute(w, record); err != nil {
|
||||
return &appError{err, "Can't display record", 500}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。
|
||||
|
||||
|
||||
40
zh/11.2.md
40
zh/11.2.md
@@ -98,31 +98,31 @@ GDB的一些常用命令如下所示
|
||||
我们通过下面这个代码来演示如何通过GDB来调试Go程序,下面是将要演示的代码:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func counting(c chan<- int) {
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
c <- i
|
||||
}
|
||||
close(c)
|
||||
func counting(c chan<- int) {
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
c <- i
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
msg := "Starting main"
|
||||
fmt.Println(msg)
|
||||
bus := make(chan int)
|
||||
msg = "starting a gofunc"
|
||||
go counting(bus)
|
||||
for count := range bus {
|
||||
fmt.Println("count:", count)
|
||||
}
|
||||
func main() {
|
||||
msg := "Starting main"
|
||||
fmt.Println(msg)
|
||||
bus := make(chan int)
|
||||
msg = "starting a gofunc"
|
||||
go counting(bus)
|
||||
for count := range bus {
|
||||
fmt.Println("count:", count)
|
||||
}
|
||||
}
|
||||
```
|
||||
编译文件,生成可执行文件gdbfile:
|
||||
|
||||
|
||||
116
zh/11.3.md
116
zh/11.3.md
@@ -6,7 +6,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
另外建议安装[gotests](https://github.com/cweill/gotests)插件自动生成测试代码:
|
||||
|
||||
```Go
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
go get -u -v github.com/cweill/gotests/...
|
||||
|
||||
```
|
||||
|
||||
@@ -19,19 +19,19 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
|
||||
```Go
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Division(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("除数不能为0")
|
||||
}
|
||||
|
||||
return a / b, nil
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Division(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("除数不能为0")
|
||||
}
|
||||
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -49,23 +49,23 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
|
||||
```Go
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Division_1(t *testing.T) {
|
||||
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
|
||||
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("第一个测试通过了") //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
t.Error("就是不通过")
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Division_1(t *testing.T) {
|
||||
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
|
||||
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("第一个测试通过了") //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
t.Error("就是不通过")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -91,13 +91,13 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
|
||||
```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.") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("one test passed.", e) //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
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.") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("one test passed.", e) //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
```
|
||||
然后我们执行`go test -v`,就显示如下信息,测试通过了:
|
||||
|
||||
@@ -116,7 +116,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
- 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母
|
||||
|
||||
```Go
|
||||
func BenchmarkXXX(b *testing.B) { ... }
|
||||
func BenchmarkXXX(b *testing.B) { ... }
|
||||
```
|
||||
|
||||
- `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数
|
||||
@@ -127,29 +127,29 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`
|
||||
|
||||
```Go
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Division(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ { //use b.N for looping
|
||||
Division(4, 5)
|
||||
}
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Division(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ { //use b.N for looping
|
||||
Division(4, 5)
|
||||
}
|
||||
|
||||
func Benchmark_TimeConsumingFunction(b *testing.B) {
|
||||
b.StopTimer() //调用该函数停止压力测试的时间计数
|
||||
|
||||
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
|
||||
//这样这些时间不影响我们测试函数本身的性能
|
||||
|
||||
b.StartTimer() //重新开始时间
|
||||
for i := 0; i < b.N; i++ {
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TimeConsumingFunction(b *testing.B) {
|
||||
b.StopTimer() //调用该函数停止压力测试的时间计数
|
||||
|
||||
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
|
||||
//这样这些时间不影响我们测试函数本身的性能
|
||||
|
||||
b.StartTimer() //重新开始时间
|
||||
for i := 0; i < b.N; i++ {
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
250
zh/12.1.md
250
zh/12.1.md
@@ -7,7 +7,7 @@ logrus是用Go语言实现的一个日志系统,与标准库log完全兼容并
|
||||
首先安装logrus
|
||||
```Go
|
||||
|
||||
go get -u github.com/sirupsen/logrus
|
||||
go get -u github.com/sirupsen/logrus
|
||||
|
||||
```
|
||||
|
||||
@@ -15,65 +15,65 @@ logrus是用Go语言实现的一个日志系统,与标准库log完全兼容并
|
||||
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
### 基于logrus的自定义日志处理
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 日志格式化为JSON而不是默认的ASCII
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
func init() {
|
||||
// 日志格式化为JSON而不是默认的ASCII
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// 输出stdout而不是默认的stderr,也可以是一个文件
|
||||
log.SetOutput(os.Stdout)
|
||||
// 输出stdout而不是默认的stderr,也可以是一个文件
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// 只记录严重或以上警告
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
// 只记录严重或以上警告
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// 通过日志语句重用字段
|
||||
// logrus.Entry返回自WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
// 通过日志语句重用字段
|
||||
// logrus.Entry返回自WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
## seelog介绍
|
||||
@@ -95,20 +95,20 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函
|
||||
首先安装seelog
|
||||
```Go
|
||||
|
||||
go get -u github.com/cihub/seelog
|
||||
go get -u github.com/cihub/seelog
|
||||
|
||||
```
|
||||
然后我们来看一个简单的例子:
|
||||
```Go
|
||||
|
||||
package main
|
||||
package main
|
||||
|
||||
import log "github.com/cihub/seelog"
|
||||
import log "github.com/cihub/seelog"
|
||||
|
||||
func main() {
|
||||
defer log.Flush()
|
||||
log.Info("Hello from Seelog!")
|
||||
}
|
||||
func main() {
|
||||
defer log.Flush()
|
||||
log.Info("Hello from Seelog!")
|
||||
}
|
||||
|
||||
```
|
||||
编译后运行如果出现了`Hello from seelog`,说明seelog日志系统已经成功安装并且可以正常运行了。
|
||||
@@ -117,59 +117,59 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函
|
||||
seelog支持自定义日志处理,下面是我基于它自定义的日志处理包的部分内容:
|
||||
```Go
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
"fmt"
|
||||
seelog "github.com/cihub/seelog"
|
||||
// "io"
|
||||
)
|
||||
|
||||
var Logger seelog.LoggerInterface
|
||||
|
||||
func loadAppConfig() {
|
||||
appConfig := `
|
||||
<seelog minlevel="warn">
|
||||
<outputs formatid="common">
|
||||
<rollingfile type="size" filename="/data/logs/roll.log" maxsize="100000" maxrolls="5"/>
|
||||
<filter levels="critical">
|
||||
<file path="/data/logs/critical.log" formatid="critical"/>
|
||||
<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>
|
||||
</filter>
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="common" format="%Date/%Time [%LEV] %Msg%n" />
|
||||
<format id="critical" format="%File %FullPath %Func %Msg%n" />
|
||||
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
|
||||
</formats>
|
||||
</seelog>
|
||||
`
|
||||
logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
UseLogger(logger)
|
||||
}
|
||||
|
||||
func init() {
|
||||
DisableLog()
|
||||
loadAppConfig()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output
|
||||
func DisableLog() {
|
||||
Logger = seelog.Disabled
|
||||
}
|
||||
|
||||
// UseLogger uses a specified seelog.LoggerInterface to output library log.
|
||||
// Use this func if you are using Seelog logging system in your app.
|
||||
func UseLogger(newLogger seelog.LoggerInterface) {
|
||||
Logger = newLogger
|
||||
package logs
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
"fmt"
|
||||
seelog "github.com/cihub/seelog"
|
||||
// "io"
|
||||
)
|
||||
|
||||
var Logger seelog.LoggerInterface
|
||||
|
||||
func loadAppConfig() {
|
||||
appConfig := `
|
||||
<seelog minlevel="warn">
|
||||
<outputs formatid="common">
|
||||
<rollingfile type="size" filename="/data/logs/roll.log" maxsize="100000" maxrolls="5"/>
|
||||
<filter levels="critical">
|
||||
<file path="/data/logs/critical.log" formatid="critical"/>
|
||||
<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>
|
||||
</filter>
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="common" format="%Date/%Time [%LEV] %Msg%n" />
|
||||
<format id="critical" format="%File %FullPath %Func %Msg%n" />
|
||||
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
|
||||
</formats>
|
||||
</seelog>
|
||||
`
|
||||
logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
UseLogger(logger)
|
||||
}
|
||||
|
||||
func init() {
|
||||
DisableLog()
|
||||
loadAppConfig()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output
|
||||
func DisableLog() {
|
||||
Logger = seelog.Disabled
|
||||
}
|
||||
|
||||
// UseLogger uses a specified seelog.LoggerInterface to output library log.
|
||||
// Use this func if you are using Seelog logging system in your app.
|
||||
func UseLogger(newLogger seelog.LoggerInterface) {
|
||||
Logger = newLogger
|
||||
}
|
||||
```
|
||||
上面主要实现了三个函数,
|
||||
|
||||
@@ -198,36 +198,36 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处
|
||||
上面我们定义了一个自定义的日志处理包,下面就是使用示例:
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"project/logs"
|
||||
"project/configs"
|
||||
"project/routes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr, _ := configs.MainConfig.String("server", "addr")
|
||||
logs.Logger.Info("Start server at:%v", addr)
|
||||
err := http.ListenAndServe(addr, routes.NewMux())
|
||||
logs.Logger.Critical("Server err:%v", err)
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"project/logs"
|
||||
"project/configs"
|
||||
"project/routes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr, _ := configs.MainConfig.String("server", "addr")
|
||||
logs.Logger.Info("Start server at:%v", addr)
|
||||
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>
|
||||
<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")
|
||||
logs.Logger.Critical("test Critical message")
|
||||
```
|
||||
现在,只要我们的应用在线上记录一个Critical的信息,你的邮箱就会收到一个Email,这样一旦线上的系统出现问题,你就能立马通过邮件获知,就能及时的进行处理。
|
||||
## 使用应用日志
|
||||
@@ -236,8 +236,8 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处
|
||||
举一个例子,我们需要跟踪用户尝试登陆系统的操作。这里会把成功与不成功的尝试都记录下来。记录成功的使用"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
|
||||
# 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,这样就能保证日志文件不会因为不断变大而导致我们的磁盘空间不够引起问题。
|
||||
|
||||
|
||||
134
zh/12.2.md
134
zh/12.2.md
@@ -33,79 +33,79 @@
|
||||
|
||||
```html
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>找不到页面</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>找不到页面</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span10">
|
||||
<div class="hero-unit">
|
||||
<h1>404!</h1>
|
||||
<p>{{.ErrorInfo}}</p>
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span10">
|
||||
<div class="hero-unit">
|
||||
<h1>404!</h1>
|
||||
<p>{{.ErrorInfo}}</p>
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
另一个源码:
|
||||
|
||||
```html
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>系统错误页面</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>系统错误页面</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span10">
|
||||
<div class="hero-unit">
|
||||
<h1>系统暂时不可用!</h1>
|
||||
<p>{{.ErrorInfo}}</p>
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span10">
|
||||
<div class="hero-unit">
|
||||
<h1>系统暂时不可用!</h1>
|
||||
<p>{{.ErrorInfo}}</p>
|
||||
</div>
|
||||
</div><!--/span-->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
404的错误处理逻辑,如果是系统的错误也是类似的操作,同时我们看到在:
|
||||
|
||||
```Go
|
||||
|
||||
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
sayhelloName(w, r)
|
||||
return
|
||||
}
|
||||
NotFound404(w, r)
|
||||
return
|
||||
}
|
||||
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
sayhelloName(w, r)
|
||||
return
|
||||
}
|
||||
NotFound404(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
func NotFound404(w http.ResponseWriter, r *http.Request) {
|
||||
log.Error("页面找不到") //记录错误日志
|
||||
t, _ = t.ParseFiles("tmpl/404.html", nil) //解析模板文件
|
||||
ErrorInfo := "文件找不到" //获取当前用户信息
|
||||
t.Execute(w, ErrorInfo) //执行模板的merger操作
|
||||
}
|
||||
func NotFound404(w http.ResponseWriter, r *http.Request) {
|
||||
log.Error("页面找不到") //记录错误日志
|
||||
t, _ = t.ParseFiles("tmpl/404.html", nil) //解析模板文件
|
||||
ErrorInfo := "文件找不到" //获取当前用户信息
|
||||
t.Execute(w, ErrorInfo) //执行模板的merger操作
|
||||
}
|
||||
|
||||
func SystemError(w http.ResponseWriter, r *http.Request) {
|
||||
log.Critical("系统错误") //系统错误触发了Critical,那么不仅会记录日志还会发送邮件
|
||||
t, _ = t.ParseFiles("tmpl/error.html", nil) //解析模板文件
|
||||
ErrorInfo := "系统暂时不可用" //获取当前用户信息
|
||||
t.Execute(w, ErrorInfo) //执行模板的merger操作
|
||||
}
|
||||
func SystemError(w http.ResponseWriter, r *http.Request) {
|
||||
log.Critical("系统错误") //系统错误触发了Critical,那么不仅会记录日志还会发送邮件
|
||||
t, _ = t.ParseFiles("tmpl/error.html", nil) //解析模板文件
|
||||
ErrorInfo := "系统暂时不可用" //获取当前用户信息
|
||||
t.Execute(w, ErrorInfo) //执行模板的merger操作
|
||||
}
|
||||
|
||||
```
|
||||
## 如何处理异常
|
||||
@@ -114,16 +114,16 @@
|
||||
但是还有一种情况,有一些操作几乎不可能失败,而且在一些特定的情况下也没有办法返回错误,也无法继续执行,这样情况就应该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() {
|
||||
if x := recover(); x != nil {
|
||||
username = ""
|
||||
}
|
||||
}()
|
||||
func GetUser(uid int) (username string) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
username = ""
|
||||
}
|
||||
}()
|
||||
|
||||
username = User[uid]
|
||||
return
|
||||
}
|
||||
username = User[uid]
|
||||
return
|
||||
}
|
||||
```
|
||||
上面介绍了错误和异常的区别,那么我们在开发程序的时候如何来设计呢?规则很简单:如果你定义的函数有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。
|
||||
|
||||
|
||||
254
zh/12.3.md
254
zh/12.3.md
@@ -9,104 +9,104 @@
|
||||
|
||||
```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],
|
||||
"-close-fds",
|
||||
"-addr", *addr,
|
||||
"-call", *call,
|
||||
)
|
||||
serr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
s, err := ioutil.ReadAll(serr)
|
||||
s = bytes.TrimSpace(s)
|
||||
if bytes.HasPrefix(s, []byte("addr: ")) {
|
||||
fmt.Println(string(s))
|
||||
cmd.Process.Release()
|
||||
} else {
|
||||
log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err)
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
|
||||
if *d {
|
||||
cmd := exec.Command(os.Args[0],
|
||||
"-close-fds",
|
||||
"-addr", *addr,
|
||||
"-call", *call,
|
||||
)
|
||||
serr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
s, err := ioutil.ReadAll(serr)
|
||||
s = bytes.TrimSpace(s)
|
||||
if bytes.HasPrefix(s, []byte("addr: ")) {
|
||||
fmt.Println(string(s))
|
||||
cmd.Process.Release()
|
||||
} else {
|
||||
log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err)
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 另一种是利用syscall的方案,但是这个方案并不完善:
|
||||
```Go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func daemon(nochdir, noclose int) int {
|
||||
var ret, ret2 uintptr
|
||||
var err uintptr
|
||||
|
||||
darwin := syscall.OS == "darwin"
|
||||
|
||||
// already a daemon
|
||||
if syscall.Getppid() == 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// fork off the parent process
|
||||
ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
|
||||
if err != 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// failure
|
||||
if ret2 < 0 {
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// handle exception for darwin
|
||||
if darwin && ret2 == 1 {
|
||||
ret = 0
|
||||
}
|
||||
|
||||
// if we got a good PID, then we call exit the parent process.
|
||||
if ret > 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
/* Change the file mode mask */
|
||||
_ = syscall.Umask(0)
|
||||
|
||||
// create a new SID for the child process
|
||||
s_ret, s_errno := syscall.Setsid()
|
||||
if s_errno != 0 {
|
||||
log.Printf("Error: syscall.Setsid errno: %d", s_errno)
|
||||
}
|
||||
if s_ret < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if nochdir == 0 {
|
||||
os.Chdir("/")
|
||||
}
|
||||
|
||||
if noclose == 0 {
|
||||
f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
|
||||
if e == nil {
|
||||
fd := f.Fd()
|
||||
syscall.Dup2(fd, os.Stdin.Fd())
|
||||
syscall.Dup2(fd, os.Stdout.Fd())
|
||||
syscall.Dup2(fd, os.Stderr.Fd())
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func daemon(nochdir, noclose int) int {
|
||||
var ret, ret2 uintptr
|
||||
var err uintptr
|
||||
|
||||
darwin := syscall.OS == "darwin"
|
||||
|
||||
// already a daemon
|
||||
if syscall.Getppid() == 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// fork off the parent process
|
||||
ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
|
||||
if err != 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// failure
|
||||
if ret2 < 0 {
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// handle exception for darwin
|
||||
if darwin && ret2 == 1 {
|
||||
ret = 0
|
||||
}
|
||||
|
||||
// if we got a good PID, then we call exit the parent process.
|
||||
if ret > 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
/* Change the file mode mask */
|
||||
_ = syscall.Umask(0)
|
||||
|
||||
// create a new SID for the child process
|
||||
s_ret, s_errno := syscall.Setsid()
|
||||
if s_errno != 0 {
|
||||
log.Printf("Error: syscall.Setsid errno: %d", s_errno)
|
||||
}
|
||||
if s_ret < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if nochdir == 0 {
|
||||
os.Chdir("/")
|
||||
}
|
||||
|
||||
if noclose == 0 {
|
||||
f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
|
||||
if e == nil {
|
||||
fd := f.Fd()
|
||||
syscall.Dup2(fd, os.Stdin.Fd())
|
||||
syscall.Dup2(fd, os.Stdout.Fd())
|
||||
syscall.Dup2(fd, os.Stderr.Fd())
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
上面提出了两种实现Go的daemon方案,但是我还是不推荐大家这样去实现,因为官方还没有正式的宣布支持daemon,当然第一种方案目前来看是比较可行的,而且目前开源库skynet也在采用这个方案做daemon。
|
||||
@@ -128,45 +128,45 @@ Supervisord默认的配置文件路径为/etc/supervisord.conf,通过文本编
|
||||
|
||||
```conf
|
||||
|
||||
;/etc/supervisord.conf
|
||||
[unix_http_server]
|
||||
file = /var/run/supervisord.sock
|
||||
chmod = 0777
|
||||
chown= root:root
|
||||
;/etc/supervisord.conf
|
||||
[unix_http_server]
|
||||
file = /var/run/supervisord.sock
|
||||
chmod = 0777
|
||||
chown= root:root
|
||||
|
||||
[inet_http_server]
|
||||
# Web管理界面设定
|
||||
port=9001
|
||||
username = admin
|
||||
password = yourpassword
|
||||
[inet_http_server]
|
||||
# Web管理界面设定
|
||||
port=9001
|
||||
username = admin
|
||||
password = yourpassword
|
||||
|
||||
[supervisorctl]
|
||||
; 必须和'unix_http_server'里面的设定匹配
|
||||
serverurl = unix:///var/run/supervisord.sock
|
||||
[supervisorctl]
|
||||
; 必须和'unix_http_server'里面的设定匹配
|
||||
serverurl = unix:///var/run/supervisord.sock
|
||||
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
|
||||
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
|
||||
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
|
||||
loglevel=info ; (log level;default info; others: debug,warn,trace)
|
||||
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
|
||||
nodaemon=true ; (start in foreground if true;default false)
|
||||
minfds=1024 ; (min. avail startup file descriptors;default 1024)
|
||||
minprocs=200 ; (min. avail process descriptors;default 200)
|
||||
user=root ; (default is current user, required if root)
|
||||
childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
|
||||
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
|
||||
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
|
||||
loglevel=info ; (log level;default info; others: debug,warn,trace)
|
||||
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
|
||||
nodaemon=true ; (start in foreground if true;default false)
|
||||
minfds=1024 ; (min. avail startup file descriptors;default 1024)
|
||||
minprocs=200 ; (min. avail process descriptors;default 200)
|
||||
user=root ; (default is current user, required if root)
|
||||
childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
; 管理的单个进程的配置,可以添加多个program
|
||||
[program:blogdemon]
|
||||
command=/data/blog/blogdemon
|
||||
autostart = true
|
||||
startsecs = 5
|
||||
user = root
|
||||
redirect_stderr = true
|
||||
stdout_logfile = /var/log/supervisord/blogdemon.log
|
||||
; 管理的单个进程的配置,可以添加多个program
|
||||
[program:blogdemon]
|
||||
command=/data/blog/blogdemon
|
||||
autostart = true
|
||||
startsecs = 5
|
||||
user = root
|
||||
redirect_stderr = true
|
||||
stdout_logfile = /var/log/supervisord/blogdemon.log
|
||||
|
||||
```
|
||||
### Supervisord管理
|
||||
|
||||
350
zh/13.2.md
350
zh/13.2.md
@@ -11,17 +11,17 @@ 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))
|
||||
}
|
||||
func fooHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
}
|
||||
|
||||
http.HandleFunc("/foo", fooHandler)
|
||||
http.HandleFunc("/foo", fooHandler)
|
||||
|
||||
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
})
|
||||
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
|
||||
```
|
||||
上面的例子调用了http默认的DefaultServeMux来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点:
|
||||
@@ -34,15 +34,15 @@ Go默认的路由添加是通过函数`http.Handle`和`http.HandleFunc`等来添
|
||||
Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度,遍历之前存储的map路由信息,和用户访问的URL进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。
|
||||
```Go
|
||||
|
||||
for k, v := range mux.m {
|
||||
if !pathMatch(k, path) {
|
||||
continue
|
||||
}
|
||||
if h == nil || len(k) > n {
|
||||
n = len(k)
|
||||
h = v.h
|
||||
}
|
||||
for k, v := range mux.m {
|
||||
if !pathMatch(k, path) {
|
||||
continue
|
||||
}
|
||||
if h == nil || len(k) > n {
|
||||
n = len(k)
|
||||
h = v.h
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## beego框架路由实现
|
||||
@@ -60,197 +60,197 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方
|
||||
根据上面的思路,我们设计了两个数据类型controllerInfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息)
|
||||
```Go
|
||||
|
||||
type controllerInfo struct {
|
||||
regex *regexp.Regexp
|
||||
params map[int]string
|
||||
controllerType reflect.Type
|
||||
}
|
||||
type controllerInfo struct {
|
||||
regex *regexp.Regexp
|
||||
params map[int]string
|
||||
controllerType reflect.Type
|
||||
}
|
||||
|
||||
type ControllerRegistor struct {
|
||||
routers []*controllerInfo
|
||||
Application *App
|
||||
}
|
||||
|
||||
type ControllerRegistor struct {
|
||||
routers []*controllerInfo
|
||||
Application *App
|
||||
}
|
||||
|
||||
```
|
||||
ControllerRegistor对外的接口函数有
|
||||
```Go
|
||||
|
||||
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface)
|
||||
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface)
|
||||
```
|
||||
详细的实现如下所示:
|
||||
```Go
|
||||
|
||||
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
|
||||
parts := strings.Split(pattern, "/")
|
||||
|
||||
j := 0
|
||||
params := make(map[int]string)
|
||||
for i, part := range parts {
|
||||
if strings.HasPrefix(part, ":") {
|
||||
expr := "([^/]+)"
|
||||
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
|
||||
parts := strings.Split(pattern, "/")
|
||||
|
||||
//a user may choose to override the defult expression
|
||||
// similar to expressjs: ‘/user/:id([0-9]+)’
|
||||
j := 0
|
||||
params := make(map[int]string)
|
||||
for i, part := range parts {
|
||||
if strings.HasPrefix(part, ":") {
|
||||
expr := "([^/]+)"
|
||||
|
||||
//a user may choose to override the defult expression
|
||||
// similar to expressjs: ‘/user/:id([0-9]+)’
|
||||
|
||||
if index := strings.Index(part, "("); index != -1 {
|
||||
expr = part[index:]
|
||||
part = part[:index]
|
||||
}
|
||||
params[j] = part
|
||||
parts[i] = expr
|
||||
j++
|
||||
if index := strings.Index(part, "("); index != -1 {
|
||||
expr = part[index:]
|
||||
part = part[:index]
|
||||
}
|
||||
params[j] = part
|
||||
parts[i] = expr
|
||||
j++
|
||||
}
|
||||
|
||||
//recreate the url pattern, with parameters replaced
|
||||
//by regular expressions. then compile the regex
|
||||
|
||||
pattern = strings.Join(parts, "/")
|
||||
regex, regexErr := regexp.Compile(pattern)
|
||||
if regexErr != nil {
|
||||
|
||||
//TODO add error handling here to avoid panic
|
||||
panic(regexErr)
|
||||
return
|
||||
}
|
||||
|
||||
//now create the Route
|
||||
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||
route := &controllerInfo{}
|
||||
route.regex = regex
|
||||
route.params = params
|
||||
route.controllerType = t
|
||||
|
||||
p.routers = append(p.routers, route)
|
||||
|
||||
}
|
||||
|
||||
//recreate the url pattern, with parameters replaced
|
||||
//by regular expressions. then compile the regex
|
||||
|
||||
pattern = strings.Join(parts, "/")
|
||||
regex, regexErr := regexp.Compile(pattern)
|
||||
if regexErr != nil {
|
||||
|
||||
//TODO add error handling here to avoid panic
|
||||
panic(regexErr)
|
||||
return
|
||||
}
|
||||
|
||||
//now create the Route
|
||||
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||
route := &controllerInfo{}
|
||||
route.regex = regex
|
||||
route.params = params
|
||||
route.controllerType = t
|
||||
|
||||
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
|
||||
}
|
||||
func (app *App) SetStaticPath(url string, path string) *App {
|
||||
StaticDir[url] = path
|
||||
return app
|
||||
}
|
||||
```
|
||||
应用中设置静态路径可以使用如下方式实现:
|
||||
```Go
|
||||
|
||||
beego.SetStaticPath("/img","/static/img")
|
||||
|
||||
beego.SetStaticPath("/img","/static/img")
|
||||
|
||||
```
|
||||
### 转发路由
|
||||
转发路由是基于ControllerRegistor里的路由信息来进行转发的,详细的实现如下代码所示:
|
||||
```Go
|
||||
|
||||
// AutoRoute
|
||||
func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if !RecoverPanic {
|
||||
// go back to panic
|
||||
panic(err)
|
||||
} else {
|
||||
Critical("Handler crashed with error", err)
|
||||
for i := 1; ; i += 1 {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
Critical(file, line)
|
||||
// AutoRoute
|
||||
func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if !RecoverPanic {
|
||||
// go back to panic
|
||||
panic(err)
|
||||
} else {
|
||||
Critical("Handler crashed with error", err)
|
||||
for i := 1; ; i += 1 {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
Critical(file, line)
|
||||
}
|
||||
}
|
||||
}()
|
||||
var started bool
|
||||
for prefix, staticDir := range StaticDir {
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
file := staticDir + r.URL.Path[len(prefix):]
|
||||
http.ServeFile(w, r, file)
|
||||
started = true
|
||||
return
|
||||
}
|
||||
}
|
||||
requestPath := r.URL.Path
|
||||
|
||||
//find a matching Route
|
||||
for _, route := range p.routers {
|
||||
|
||||
//check if Route pattern matches url
|
||||
if !route.regex.MatchString(requestPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
//get submatches (params)
|
||||
matches := route.regex.FindStringSubmatch(requestPath)
|
||||
|
||||
//double check that the Route matches the URL pattern.
|
||||
if len(matches[0]) != len(requestPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
if len(route.params) > 0 {
|
||||
//add url parameters to the query param map
|
||||
values := r.URL.Query()
|
||||
for i, match := range matches[1:] {
|
||||
values.Add(route.params[i], match)
|
||||
params[route.params[i]] = match
|
||||
}
|
||||
|
||||
//reassemble query params and add to RawQuery
|
||||
r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
|
||||
//r.URL.RawQuery = url.Values(values).Encode()
|
||||
}
|
||||
//Invoke the request handler
|
||||
vc := reflect.New(route.controllerType)
|
||||
init := vc.MethodByName("Init")
|
||||
in := make([]reflect.Value, 2)
|
||||
ct := &Context{ResponseWriter: w, Request: r, Params: params}
|
||||
in[0] = reflect.ValueOf(ct)
|
||||
in[1] = reflect.ValueOf(route.controllerType.Name())
|
||||
init.Call(in)
|
||||
in = make([]reflect.Value, 0)
|
||||
method := vc.MethodByName("Prepare")
|
||||
method.Call(in)
|
||||
if r.Method == "GET" {
|
||||
method = vc.MethodByName("Get")
|
||||
method.Call(in)
|
||||
} else if r.Method == "POST" {
|
||||
method = vc.MethodByName("Post")
|
||||
method.Call(in)
|
||||
} else if r.Method == "HEAD" {
|
||||
method = vc.MethodByName("Head")
|
||||
method.Call(in)
|
||||
} else if r.Method == "DELETE" {
|
||||
method = vc.MethodByName("Delete")
|
||||
method.Call(in)
|
||||
} else if r.Method == "PUT" {
|
||||
method = vc.MethodByName("Put")
|
||||
method.Call(in)
|
||||
} else if r.Method == "PATCH" {
|
||||
method = vc.MethodByName("Patch")
|
||||
method.Call(in)
|
||||
} else if r.Method == "OPTIONS" {
|
||||
method = vc.MethodByName("Options")
|
||||
method.Call(in)
|
||||
}
|
||||
if AutoRender {
|
||||
method = vc.MethodByName("Render")
|
||||
method.Call(in)
|
||||
}
|
||||
method = vc.MethodByName("Finish")
|
||||
method.Call(in)
|
||||
}()
|
||||
var started bool
|
||||
for prefix, staticDir := range StaticDir {
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
file := staticDir + r.URL.Path[len(prefix):]
|
||||
http.ServeFile(w, r, file)
|
||||
started = true
|
||||
break
|
||||
}
|
||||
|
||||
//if no matches to url, throw a not found exception
|
||||
if started == false {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
requestPath := r.URL.Path
|
||||
|
||||
//find a matching Route
|
||||
for _, route := range p.routers {
|
||||
|
||||
//check if Route pattern matches url
|
||||
if !route.regex.MatchString(requestPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
//get submatches (params)
|
||||
matches := route.regex.FindStringSubmatch(requestPath)
|
||||
|
||||
//double check that the Route matches the URL pattern.
|
||||
if len(matches[0]) != len(requestPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
if len(route.params) > 0 {
|
||||
//add url parameters to the query param map
|
||||
values := r.URL.Query()
|
||||
for i, match := range matches[1:] {
|
||||
values.Add(route.params[i], match)
|
||||
params[route.params[i]] = match
|
||||
}
|
||||
|
||||
//reassemble query params and add to RawQuery
|
||||
r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
|
||||
//r.URL.RawQuery = url.Values(values).Encode()
|
||||
}
|
||||
//Invoke the request handler
|
||||
vc := reflect.New(route.controllerType)
|
||||
init := vc.MethodByName("Init")
|
||||
in := make([]reflect.Value, 2)
|
||||
ct := &Context{ResponseWriter: w, Request: r, Params: params}
|
||||
in[0] = reflect.ValueOf(ct)
|
||||
in[1] = reflect.ValueOf(route.controllerType.Name())
|
||||
init.Call(in)
|
||||
in = make([]reflect.Value, 0)
|
||||
method := vc.MethodByName("Prepare")
|
||||
method.Call(in)
|
||||
if r.Method == "GET" {
|
||||
method = vc.MethodByName("Get")
|
||||
method.Call(in)
|
||||
} else if r.Method == "POST" {
|
||||
method = vc.MethodByName("Post")
|
||||
method.Call(in)
|
||||
} else if r.Method == "HEAD" {
|
||||
method = vc.MethodByName("Head")
|
||||
method.Call(in)
|
||||
} else if r.Method == "DELETE" {
|
||||
method = vc.MethodByName("Delete")
|
||||
method.Call(in)
|
||||
} else if r.Method == "PUT" {
|
||||
method = vc.MethodByName("Put")
|
||||
method.Call(in)
|
||||
} else if r.Method == "PATCH" {
|
||||
method = vc.MethodByName("Patch")
|
||||
method.Call(in)
|
||||
} else if r.Method == "OPTIONS" {
|
||||
method = vc.MethodByName("Options")
|
||||
method.Call(in)
|
||||
}
|
||||
if AutoRender {
|
||||
method = vc.MethodByName("Render")
|
||||
method.Call(in)
|
||||
}
|
||||
method = vc.MethodByName("Finish")
|
||||
method.Call(in)
|
||||
started = true
|
||||
break
|
||||
}
|
||||
|
||||
//if no matches to url, throw a not found exception
|
||||
if started == false {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
```
|
||||
### 使用入门
|
||||
基于这样的路由设计之后就可以解决前面所说的三个限制点,使用的方式如下所示:
|
||||
@@ -258,17 +258,17 @@ ControllerRegistor对外的接口函数有
|
||||
基本的使用注册路由:
|
||||
```Go
|
||||
|
||||
beego.BeeApp.RegisterController("/", &controllers.MainController{})
|
||||
beego.BeeApp.RegisterController("/", &controllers.MainController{})
|
||||
```
|
||||
参数注册:
|
||||
```Go
|
||||
|
||||
beego.BeeApp.RegisterController("/:param", &controllers.UserController{})
|
||||
beego.BeeApp.RegisterController("/:param", &controllers.UserController{})
|
||||
```
|
||||
正则匹配:
|
||||
```Go
|
||||
|
||||
beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{})
|
||||
beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{})
|
||||
```
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
224
zh/13.3.md
224
zh/13.3.md
@@ -10,140 +10,140 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分
|
||||
前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface
|
||||
```Go
|
||||
|
||||
type Controller struct {
|
||||
Ct *Context
|
||||
Tpl *template.Template
|
||||
Data map[interface{}]interface{}
|
||||
ChildName string
|
||||
TplNames string
|
||||
Layout []string
|
||||
TplExt string
|
||||
}
|
||||
type Controller struct {
|
||||
Ct *Context
|
||||
Tpl *template.Template
|
||||
Data map[interface{}]interface{}
|
||||
ChildName string
|
||||
TplNames string
|
||||
Layout []string
|
||||
TplExt string
|
||||
}
|
||||
|
||||
type ControllerInterface interface {
|
||||
Init(ct *Context, cn string) //初始化上下文和子类名称
|
||||
Prepare() //开始执行之前的一些处理
|
||||
Get() //method=GET的处理
|
||||
Post() //method=POST的处理
|
||||
Delete() //method=DELETE的处理
|
||||
Put() //method=PUT的处理
|
||||
Head() //method=HEAD的处理
|
||||
Patch() //method=PATCH的处理
|
||||
Options() //method=OPTIONS的处理
|
||||
Finish() //执行完成之后的处理
|
||||
Render() error //执行完method对应的方法之后渲染页面
|
||||
}
|
||||
type ControllerInterface interface {
|
||||
Init(ct *Context, cn string) //初始化上下文和子类名称
|
||||
Prepare() //开始执行之前的一些处理
|
||||
Get() //method=GET的处理
|
||||
Post() //method=POST的处理
|
||||
Delete() //method=DELETE的处理
|
||||
Put() //method=PUT的处理
|
||||
Head() //method=HEAD的处理
|
||||
Patch() //method=PATCH的处理
|
||||
Options() //method=OPTIONS的处理
|
||||
Finish() //执行完成之后的处理
|
||||
Render() error //执行完method对应的方法之后渲染页面
|
||||
}
|
||||
```
|
||||
那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法:
|
||||
```Go
|
||||
|
||||
func (c *Controller) Init(ct *Context, cn string) {
|
||||
c.Data = make(map[interface{}]interface{})
|
||||
c.Layout = make([]string, 0)
|
||||
c.TplNames = ""
|
||||
c.ChildName = cn
|
||||
c.Ct = ct
|
||||
c.TplExt = "tpl"
|
||||
}
|
||||
func (c *Controller) Init(ct *Context, cn string) {
|
||||
c.Data = make(map[interface{}]interface{})
|
||||
c.Layout = make([]string, 0)
|
||||
c.TplNames = ""
|
||||
c.ChildName = cn
|
||||
c.Ct = ct
|
||||
c.TplExt = "tpl"
|
||||
}
|
||||
|
||||
func (c *Controller) Prepare() {
|
||||
func (c *Controller) Prepare() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) Finish() {
|
||||
func (c *Controller) Finish() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) Get() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Get() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Post() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Post() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Delete() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Delete() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Put() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Put() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Head() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Head() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Patch() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Patch() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Options() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
func (c *Controller) Options() {
|
||||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
func (c *Controller) Render() error {
|
||||
if len(c.Layout) > 0 {
|
||||
var filenames []string
|
||||
for _, file := range c.Layout {
|
||||
filenames = append(filenames, path.Join(ViewsPath, file))
|
||||
}
|
||||
t, err := template.ParseFiles(filenames...)
|
||||
if err != nil {
|
||||
Trace("template ParseFiles err:", err)
|
||||
}
|
||||
err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
}
|
||||
} else {
|
||||
if c.TplNames == "" {
|
||||
c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
|
||||
}
|
||||
t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames))
|
||||
if err != nil {
|
||||
Trace("template ParseFiles err:", err)
|
||||
}
|
||||
err = t.Execute(c.Ct.ResponseWriter, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
}
|
||||
func (c *Controller) Render() error {
|
||||
if len(c.Layout) > 0 {
|
||||
var filenames []string
|
||||
for _, file := range c.Layout {
|
||||
filenames = append(filenames, path.Join(ViewsPath, file))
|
||||
}
|
||||
t, err := template.ParseFiles(filenames...)
|
||||
if err != nil {
|
||||
Trace("template ParseFiles err:", err)
|
||||
}
|
||||
err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
}
|
||||
} else {
|
||||
if c.TplNames == "" {
|
||||
c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
|
||||
}
|
||||
t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames))
|
||||
if err != nil {
|
||||
Trace("template ParseFiles err:", err)
|
||||
}
|
||||
err = t.Execute(c.Ct.ResponseWriter, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) Redirect(url string, code int) {
|
||||
c.Ct.Redirect(code, url)
|
||||
}
|
||||
func (c *Controller) Redirect(url string, code int) {
|
||||
c.Ct.Redirect(code, url)
|
||||
}
|
||||
```
|
||||
上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下:
|
||||
```Go
|
||||
|
||||
Init() 初始化
|
||||
Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数
|
||||
method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403
|
||||
Render() 可选,根据全局变量AutoRender来判断是否执行
|
||||
Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数
|
||||
Init() 初始化
|
||||
Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数
|
||||
method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403
|
||||
Render() 可选,根据全局变量AutoRender来判断是否执行
|
||||
Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数
|
||||
```
|
||||
## 应用指南
|
||||
上面beego框架中完成了controller基类的设计,那么我们在我们的应用中可以这样来设计我们的方法:
|
||||
```Go
|
||||
|
||||
package controllers
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
func (this *MainController) Get() {
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
```
|
||||
上面的方式我们实现了子类MainController,实现了Get方法,那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回403,而如果是Get来访问,因为我们设置了AutoRender=true,那么在执行完Get方法之后会自动执行Render函数,就会显示如下界面:
|
||||
|
||||
@@ -152,16 +152,16 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分
|
||||
index.tpl的代码如下所示,我们可以看到数据的设置和显示都是相当的简单方便:
|
||||
```html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>beego welcome template</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, world!{{.Username}},{{.Email}}</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>beego welcome template</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, world!{{.Username}},{{.Email}}</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
## links
|
||||
|
||||
346
zh/13.4.md
346
zh/13.4.md
@@ -9,84 +9,84 @@
|
||||
beego的日志设计部署思路来自于seelog,根据不同的level来记录日志,但是beego设计的日志系统比较轻量级,采用了系统的log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出,详细的实现如下所示:
|
||||
|
||||
```Go
|
||||
|
||||
// Log levels to control the logging output.
|
||||
const (
|
||||
LevelTrace = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelWarning
|
||||
LevelError
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
// logLevel controls the global log level used by the logger.
|
||||
var level = LevelTrace
|
||||
|
||||
// LogLevel returns the global log level and can be used in
|
||||
// own implementations of the logger interface.
|
||||
func Level() int {
|
||||
return level
|
||||
}
|
||||
|
||||
// SetLogLevel sets the global log level used by the simple
|
||||
// logger.
|
||||
func SetLevel(l int) {
|
||||
level = l
|
||||
}
|
||||
|
||||
// Log levels to control the logging output.
|
||||
const (
|
||||
LevelTrace = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelWarning
|
||||
LevelError
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
// logLevel controls the global log level used by the logger.
|
||||
var level = LevelTrace
|
||||
|
||||
// LogLevel returns the global log level and can be used in
|
||||
// own implementations of the logger interface.
|
||||
func Level() int {
|
||||
return level
|
||||
}
|
||||
|
||||
// SetLogLevel sets the global log level used by the simple
|
||||
// logger.
|
||||
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)
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(l *log.Logger) {
|
||||
BeeLogger = l
|
||||
// logger references the used application logger.
|
||||
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(l *log.Logger) {
|
||||
BeeLogger = l
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
func Trace(v ...interface{}) {
|
||||
if level <= LevelTrace {
|
||||
BeeLogger.Printf("[T] %v\n", v)
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
func Trace(v ...interface{}) {
|
||||
if level <= LevelTrace {
|
||||
BeeLogger.Printf("[T] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(v ...interface{}) {
|
||||
if level <= LevelDebug {
|
||||
BeeLogger.Printf("[D] %v\n", v)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(v ...interface{}) {
|
||||
if level <= LevelDebug {
|
||||
BeeLogger.Printf("[D] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Info logs a message at info level.
|
||||
func Info(v ...interface{}) {
|
||||
if level <= LevelInfo {
|
||||
BeeLogger.Printf("[I] %v\n", v)
|
||||
}
|
||||
|
||||
// Info logs a message at info level.
|
||||
func Info(v ...interface{}) {
|
||||
if level <= LevelInfo {
|
||||
BeeLogger.Printf("[I] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warn(v ...interface{}) {
|
||||
if level <= LevelWarning {
|
||||
BeeLogger.Printf("[W] %v\n", v)
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warn(v ...interface{}) {
|
||||
if level <= LevelWarning {
|
||||
BeeLogger.Printf("[W] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(v ...interface{}) {
|
||||
if level <= LevelError {
|
||||
BeeLogger.Printf("[E] %v\n", v)
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(v ...interface{}) {
|
||||
if level <= LevelError {
|
||||
BeeLogger.Printf("[E] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(v ...interface{}) {
|
||||
if level <= LevelCritical {
|
||||
BeeLogger.Printf("[C] %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(v ...interface{}) {
|
||||
if level <= LevelCritical {
|
||||
BeeLogger.Printf("[C] %v\n", v)
|
||||
}
|
||||
}
|
||||
```
|
||||
上面这一段代码默认初始化了一个BeeLogger对象,默认输出到os.Stdout,用户可以通过beego.SetLogger来设置实现了logger的接口输出。这里面实现了六个函数:
|
||||
|
||||
@@ -121,134 +121,134 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录
|
||||
首先定义了一些ini配置文件的一些全局性常量 :
|
||||
```Go
|
||||
|
||||
var (
|
||||
bComment = []byte{'#'}
|
||||
bEmpty = []byte{}
|
||||
bEqual = []byte{'='}
|
||||
bDQuote = []byte{'"'}
|
||||
)
|
||||
var (
|
||||
bComment = []byte{'#'}
|
||||
bEmpty = []byte{}
|
||||
bEqual = []byte{'='}
|
||||
bDQuote = []byte{'"'}
|
||||
)
|
||||
```
|
||||
定义了配置文件的格式:
|
||||
```Go
|
||||
|
||||
// A Config represents the configuration.
|
||||
type Config struct {
|
||||
filename string
|
||||
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
|
||||
data map[string]string // key: value
|
||||
offset map[string]int64 // key: offset; for editing.
|
||||
sync.RWMutex
|
||||
}
|
||||
// A Config represents the configuration.
|
||||
type Config struct {
|
||||
filename string
|
||||
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
|
||||
data map[string]string // key: value
|
||||
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) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
file.Name(),
|
||||
make(map[int][]string),
|
||||
make(map[string]string),
|
||||
make(map[string]int64),
|
||||
sync.RWMutex{},
|
||||
}
|
||||
cfg.Lock()
|
||||
defer cfg.Unlock()
|
||||
defer file.Close()
|
||||
|
||||
var comment bytes.Buffer
|
||||
buf := bufio.NewReader(file)
|
||||
|
||||
for nComment, off := 0, int64(1); ; {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
}
|
||||
|
||||
off += int64(len(line))
|
||||
|
||||
if bytes.HasPrefix(line, bComment) {
|
||||
line = bytes.TrimLeft(line, "#")
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
comment.Write(line)
|
||||
comment.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
if comment.Len() != 0 {
|
||||
cfg.comment[nComment] = []string{comment.String()}
|
||||
comment.Reset()
|
||||
nComment++
|
||||
}
|
||||
|
||||
val := bytes.SplitN(line, bEqual, 2)
|
||||
if bytes.HasPrefix(val[1], bDQuote) {
|
||||
val[1] = bytes.Trim(val[1], `"`)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(string(val[0]))
|
||||
cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
|
||||
cfg.data[key] = strings.TrimSpace(string(val[1]))
|
||||
cfg.offset[key] = off
|
||||
}
|
||||
return cfg, nil
|
||||
// ParseFile creates a new Config and parses the file configuration from the
|
||||
// named file.
|
||||
func LoadConfig(name string) (*Config, error) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
file.Name(),
|
||||
make(map[int][]string),
|
||||
make(map[string]string),
|
||||
make(map[string]int64),
|
||||
sync.RWMutex{},
|
||||
}
|
||||
cfg.Lock()
|
||||
defer cfg.Unlock()
|
||||
defer file.Close()
|
||||
|
||||
var comment bytes.Buffer
|
||||
buf := bufio.NewReader(file)
|
||||
|
||||
for nComment, off := 0, int64(1); ; {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
}
|
||||
|
||||
off += int64(len(line))
|
||||
|
||||
if bytes.HasPrefix(line, bComment) {
|
||||
line = bytes.TrimLeft(line, "#")
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
comment.Write(line)
|
||||
comment.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
if comment.Len() != 0 {
|
||||
cfg.comment[nComment] = []string{comment.String()}
|
||||
comment.Reset()
|
||||
nComment++
|
||||
}
|
||||
|
||||
val := bytes.SplitN(line, bEqual, 2)
|
||||
if bytes.HasPrefix(val[1], bDQuote) {
|
||||
val[1] = bytes.Trim(val[1], `"`)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(string(val[0]))
|
||||
cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
|
||||
cfg.data[key] = strings.TrimSpace(string(val[1]))
|
||||
cfg.offset[key] = off
|
||||
}
|
||||
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])
|
||||
}
|
||||
|
||||
// Int returns the integer value for a given key.
|
||||
func (c *Config) Int(key string) (int, error) {
|
||||
return strconv.Atoi(c.data[key])
|
||||
}
|
||||
|
||||
// Float returns the float value for a given key.
|
||||
func (c *Config) Float(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.data[key], 64)
|
||||
}
|
||||
|
||||
// String returns the string value for a given key.
|
||||
func (c *Config) String(key string) string {
|
||||
return c.data[key]
|
||||
}
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *Config) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.data[key])
|
||||
}
|
||||
|
||||
// Int returns the integer value for a given key.
|
||||
func (c *Config) Int(key string) (int, error) {
|
||||
return strconv.Atoi(c.data[key])
|
||||
}
|
||||
|
||||
// Float returns the float value for a given key.
|
||||
func (c *Config) Float(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.data[key], 64)
|
||||
}
|
||||
|
||||
// String returns the string value for a given key.
|
||||
func (c *Config) String(key string) string {
|
||||
return c.data[key]
|
||||
}
|
||||
```
|
||||
## 应用指南
|
||||
下面这个函数是我一个应用中的例子,用来获取远程url地址的json数据,实现如下:
|
||||
```Go
|
||||
|
||||
func GetJson() {
|
||||
resp, err := http.Get(beego.AppConfig.String("url"))
|
||||
if err != nil {
|
||||
beego.Critical("http get info error")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &AllInfo)
|
||||
if err != nil {
|
||||
beego.Critical("error:", err)
|
||||
}
|
||||
func GetJson() {
|
||||
resp, err := http.Get(beego.AppConfig.String("url"))
|
||||
if err != nil {
|
||||
beego.Critical("http get info error")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &AllInfo)
|
||||
if err != nil {
|
||||
beego.Critical("error:", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
函数中调用了框架的日志函数`beego.Critical`函数用来报错,调用了`beego.AppConfig.String("url")`用来获取配置文件中的信息,配置文件的信息如下(app.conf):
|
||||
```Go
|
||||
|
||||
appname = hs
|
||||
url ="http://www.api.com/api.html"
|
||||
|
||||
appname = hs
|
||||
url ="http://www.api.com/api.html"
|
||||
|
||||
```
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
332
zh/13.5.md
332
zh/13.5.md
@@ -26,173 +26,173 @@
|
||||
博客主要的路由规则如下所示:
|
||||
```Go
|
||||
|
||||
//显示博客首页
|
||||
beego.Router("/", &controllers.IndexController{})
|
||||
//查看博客详细信息
|
||||
beego.Router("/view/:id([0-9]+)", &controllers.ViewController{})
|
||||
//新建博客博文
|
||||
beego.Router("/new", &controllers.NewController{})
|
||||
//删除博文
|
||||
beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{})
|
||||
//编辑博文
|
||||
beego.Router("/edit/:id([0-9]+)", &controllers.EditController{})
|
||||
//显示博客首页
|
||||
beego.Router("/", &controllers.IndexController{})
|
||||
//查看博客详细信息
|
||||
beego.Router("/view/:id([0-9]+)", &controllers.ViewController{})
|
||||
//新建博客博文
|
||||
beego.Router("/new", &controllers.NewController{})
|
||||
//删除博文
|
||||
beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{})
|
||||
//编辑博文
|
||||
beego.Router("/edit/:id([0-9]+)", &controllers.EditController{})
|
||||
|
||||
```
|
||||
## 数据库结构
|
||||
数据库设计最简单的博客信息
|
||||
```sql
|
||||
|
||||
CREATE TABLE entries (
|
||||
id INT AUTO_INCREMENT,
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
created DATETIME,
|
||||
primary key (id)
|
||||
);
|
||||
CREATE TABLE entries (
|
||||
id INT AUTO_INCREMENT,
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
created DATETIME,
|
||||
primary key (id)
|
||||
);
|
||||
```
|
||||
## 控制器
|
||||
IndexController:
|
||||
|
||||
```Go
|
||||
|
||||
type IndexController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type IndexController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *IndexController) Get() {
|
||||
this.Data["blogs"] = models.GetAll()
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
func (this *IndexController) Get() {
|
||||
this.Data["blogs"] = models.GetAll()
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
```
|
||||
ViewController:
|
||||
|
||||
```Go
|
||||
|
||||
type ViewController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type ViewController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *ViewController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
this.Data["Post"] = models.GetBlog(id)
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "view.tpl"
|
||||
}
|
||||
func (this *ViewController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
this.Data["Post"] = models.GetBlog(id)
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "view.tpl"
|
||||
}
|
||||
```
|
||||
NewController
|
||||
```Go
|
||||
|
||||
type NewController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type NewController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *NewController) Get() {
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "new.tpl"
|
||||
}
|
||||
func (this *NewController) Get() {
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "new.tpl"
|
||||
}
|
||||
|
||||
func (this *NewController) Post() {
|
||||
inputs := this.Input()
|
||||
var blog models.Blog
|
||||
blog.Title = inputs.Get("title")
|
||||
blog.Content = inputs.Get("content")
|
||||
blog.Created = time.Now()
|
||||
models.SaveBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
func (this *NewController) Post() {
|
||||
inputs := this.Input()
|
||||
var blog models.Blog
|
||||
blog.Title = inputs.Get("title")
|
||||
blog.Content = inputs.Get("content")
|
||||
blog.Created = time.Now()
|
||||
models.SaveBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
```
|
||||
EditController
|
||||
```Go
|
||||
|
||||
type EditController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type EditController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *EditController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
this.Data["Post"] = models.GetBlog(id)
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "edit.tpl"
|
||||
}
|
||||
func (this *EditController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
this.Data["Post"] = models.GetBlog(id)
|
||||
this.Layout = "layout.tpl"
|
||||
this.TplNames = "edit.tpl"
|
||||
}
|
||||
|
||||
func (this *EditController) Post() {
|
||||
inputs := this.Input()
|
||||
var blog models.Blog
|
||||
blog.Id, _ = strconv.Atoi(inputs.Get("id"))
|
||||
blog.Title = inputs.Get("title")
|
||||
blog.Content = inputs.Get("content")
|
||||
blog.Created = time.Now()
|
||||
models.SaveBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
func (this *EditController) Post() {
|
||||
inputs := this.Input()
|
||||
var blog models.Blog
|
||||
blog.Id, _ = strconv.Atoi(inputs.Get("id"))
|
||||
blog.Title = inputs.Get("title")
|
||||
blog.Content = inputs.Get("content")
|
||||
blog.Created = time.Now()
|
||||
models.SaveBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
```
|
||||
DeleteController
|
||||
```Go
|
||||
|
||||
type DeleteController struct {
|
||||
beego.Controller
|
||||
}
|
||||
type DeleteController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *DeleteController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
blog := models.GetBlog(id)
|
||||
this.Data["Post"] = blog
|
||||
models.DelBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
func (this *DeleteController) Get() {
|
||||
id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"])
|
||||
blog := models.GetBlog(id)
|
||||
this.Data["Post"] = blog
|
||||
models.DelBlog(blog)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
```
|
||||
## model层
|
||||
```Go
|
||||
|
||||
package models
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/astaxie/beedb"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
"time"
|
||||
)
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/astaxie/beedb"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Blog struct {
|
||||
Id int `PK`
|
||||
Title string
|
||||
Content string
|
||||
Created time.Time
|
||||
type Blog struct {
|
||||
Id int `PK`
|
||||
Title string
|
||||
Content string
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
func GetLink() beedb.Model {
|
||||
db, err := sql.Open("mymysql", "blog/astaxie/123456")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
orm := beedb.New(db)
|
||||
return orm
|
||||
}
|
||||
|
||||
func GetLink() beedb.Model {
|
||||
db, err := sql.Open("mymysql", "blog/astaxie/123456")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
orm := beedb.New(db)
|
||||
return orm
|
||||
}
|
||||
func GetAll() (blogs []Blog) {
|
||||
db := GetLink()
|
||||
db.FindAll(&blogs)
|
||||
return
|
||||
}
|
||||
|
||||
func GetAll() (blogs []Blog) {
|
||||
db := GetLink()
|
||||
db.FindAll(&blogs)
|
||||
return
|
||||
}
|
||||
func GetBlog(id int) (blog Blog) {
|
||||
db := GetLink()
|
||||
db.Where("id=?", id).Find(&blog)
|
||||
return
|
||||
}
|
||||
|
||||
func GetBlog(id int) (blog Blog) {
|
||||
db := GetLink()
|
||||
db.Where("id=?", id).Find(&blog)
|
||||
return
|
||||
}
|
||||
func SaveBlog(blog Blog) (bg Blog) {
|
||||
db := GetLink()
|
||||
db.Save(&blog)
|
||||
return bg
|
||||
}
|
||||
|
||||
func SaveBlog(blog Blog) (bg Blog) {
|
||||
db := GetLink()
|
||||
db.Save(&blog)
|
||||
return bg
|
||||
}
|
||||
|
||||
func DelBlog(blog Blog) {
|
||||
db := GetLink()
|
||||
db.Delete(&blog)
|
||||
return
|
||||
}
|
||||
func DelBlog(blog Blog) {
|
||||
db := GetLink()
|
||||
db.Delete(&blog)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## view层
|
||||
@@ -200,75 +200,75 @@ DeleteController
|
||||
layout.tpl
|
||||
```html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>My Blog</title>
|
||||
<style>
|
||||
#menu {
|
||||
width: 200px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Blog</title>
|
||||
<style>
|
||||
#menu {
|
||||
width: 200px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul id="menu">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/new">New Post</a></li>
|
||||
</ul>
|
||||
<ul id="menu">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/new">New Post</a></li>
|
||||
</ul>
|
||||
|
||||
{{.LayoutContent}}
|
||||
{{.LayoutContent}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
index.tpl
|
||||
```html
|
||||
|
||||
<h1>Blog posts</h1>
|
||||
<h1>Blog posts</h1>
|
||||
|
||||
<ul>
|
||||
{{range .blogs}}
|
||||
<li>
|
||||
<a href="/view/{{.Id}}">{{.Title}}</a>
|
||||
from {{.Created}}
|
||||
<a href="/edit/{{.Id}}">Edit</a>
|
||||
<a href="/delete/{{.Id}}">Delete</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
<ul>
|
||||
{{range .blogs}}
|
||||
<li>
|
||||
<a href="/view/{{.Id}}">{{.Title}}</a>
|
||||
from {{.Created}}
|
||||
<a href="/edit/{{.Id}}">Edit</a>
|
||||
<a href="/delete/{{.Id}}">Delete</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
```
|
||||
view.tpl
|
||||
```html
|
||||
|
||||
<h1>{{.Post.Title}}</h1>
|
||||
{{.Post.Created}}<br/>
|
||||
<h1>{{.Post.Title}}</h1>
|
||||
{{.Post.Created}}<br/>
|
||||
|
||||
{{.Post.Content}}
|
||||
{{.Post.Content}}
|
||||
```
|
||||
new.tpl
|
||||
```html
|
||||
|
||||
<h1>New Blog Post</h1>
|
||||
<form action="" method="post">
|
||||
标题:<input type="text" name="title"><br>
|
||||
内容:<textarea name="content" colspan="3" rowspan="10"></textarea>
|
||||
<input type="submit">
|
||||
</form>
|
||||
<h1>New Blog Post</h1>
|
||||
<form action="" method="post">
|
||||
标题:<input type="text" name="title"><br>
|
||||
内容:<textarea name="content" colspan="3" rowspan="10"></textarea>
|
||||
<input type="submit">
|
||||
</form>
|
||||
```
|
||||
edit.tpl
|
||||
```html
|
||||
|
||||
<h1>Edit {{.Post.Title}}</h1>
|
||||
<h1>Edit {{.Post.Title}}</h1>
|
||||
|
||||
<h1>New Blog Post</h1>
|
||||
<form action="" method="post">
|
||||
标题:<input type="text" name="title" value="{{.Post.Title}}"><br>
|
||||
内容:<textarea name="content" colspan="3" rowspan="10">{{.Post.Content}}</textarea>
|
||||
<input type="hidden" name="id" value="{{.Post.Id}}">
|
||||
<input type="submit">
|
||||
</form>
|
||||
<h1>New Blog Post</h1>
|
||||
<form action="" method="post">
|
||||
标题:<input type="text" name="title" value="{{.Post.Title}}"><br>
|
||||
内容:<textarea name="content" colspan="3" rowspan="10">{{.Post.Content}}</textarea>
|
||||
<input type="hidden" name="id" value="{{.Post.Id}}">
|
||||
<input type="submit">
|
||||
</form>
|
||||
```
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
36
zh/14.1.md
36
zh/14.1.md
@@ -5,22 +5,22 @@
|
||||
Go的net/http包中提供了静态文件的服务,`ServeFile`和`FileServer`等函数。beego的静态文件处理就是基于这一层处理的,具体的实现如下所示:
|
||||
```Go
|
||||
|
||||
//static file server
|
||||
for prefix, staticDir := range StaticDir {
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
file := staticDir + r.URL.Path[len(prefix):]
|
||||
http.ServeFile(w, r, file)
|
||||
w.started = true
|
||||
return
|
||||
}
|
||||
//static file server
|
||||
for prefix, staticDir := range StaticDir {
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
file := staticDir + r.URL.Path[len(prefix):]
|
||||
http.ServeFile(w, r, file)
|
||||
w.started = true
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
StaticDir里面保存的是相应的url对应到静态文件所在的目录,因此在处理URL请求的时候只需要判断对应的请求地址是否包含静态处理开头的url,如果包含的话就采用http.ServeFile提供服务。
|
||||
|
||||
举例如下:
|
||||
```Go
|
||||
|
||||
beego.StaticDir["/asset"] = "/static"
|
||||
beego.StaticDir["/asset"] = "/static"
|
||||
```
|
||||
那么请求url如`http://www.beego.me/asset/bootstrap.css`就会请求`/static/bootstrap.css`来提供反馈给客户端。
|
||||
|
||||
@@ -51,20 +51,20 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对
|
||||
2. 因为beego默认设置了StaticDir的值,所以如果你的静态文件目录是static的话就无须再增加了:
|
||||
```Go
|
||||
|
||||
StaticDir["/static"] = "static"
|
||||
StaticDir["/static"] = "static"
|
||||
```
|
||||
3. 模板中使用如下的地址就可以了:
|
||||
|
||||
```html
|
||||
|
||||
//css文件
|
||||
<link href="/static/css/bootstrap.css" rel="stylesheet">
|
||||
|
||||
//js文件
|
||||
<script src="/static/js/bootstrap-transition.js"></script>
|
||||
|
||||
//图片文件
|
||||
<img src="/static/img/logo.png">
|
||||
//css文件
|
||||
<link href="/static/css/bootstrap.css" rel="stylesheet">
|
||||
|
||||
//js文件
|
||||
<script src="/static/js/bootstrap-transition.js"></script>
|
||||
|
||||
//图片文件
|
||||
<img src="/static/img/logo.png">
|
||||
```
|
||||
上面可以实现把bootstrap集成到beego中来,如下展示的图就是集成进来之后的展现效果图:
|
||||
|
||||
|
||||
116
zh/14.2.md
116
zh/14.2.md
@@ -5,56 +5,56 @@
|
||||
beego中主要有以下的全局变量来控制session处理:
|
||||
```Go
|
||||
|
||||
//related to session
|
||||
SessionOn bool // 是否开启session模块,默认不开启
|
||||
SessionProvider string // session后端提供处理模块,默认是sessionManager支持的memory
|
||||
SessionName string // 客户端保存的cookies的名称
|
||||
SessionGCMaxLifetime int64 // cookies有效期
|
||||
//related to session
|
||||
SessionOn bool // 是否开启session模块,默认不开启
|
||||
SessionProvider string // session后端提供处理模块,默认是sessionManager支持的memory
|
||||
SessionName string // 客户端保存的cookies的名称
|
||||
SessionGCMaxLifetime int64 // cookies有效期
|
||||
|
||||
GlobalSessions *session.Manager //全局session控制器
|
||||
GlobalSessions *session.Manager //全局session控制器
|
||||
```
|
||||
当然上面这些变量需要初始化值,也可以按照下面的代码来配合配置文件以设置这些值:
|
||||
```Go
|
||||
|
||||
if ar, err := AppConfig.Bool("sessionon"); err != nil {
|
||||
SessionOn = false
|
||||
} else {
|
||||
SessionOn = ar
|
||||
}
|
||||
if ar := AppConfig.String("sessionprovider"); ar == "" {
|
||||
SessionProvider = "memory"
|
||||
} else {
|
||||
SessionProvider = ar
|
||||
}
|
||||
if ar := AppConfig.String("sessionname"); ar == "" {
|
||||
SessionName = "beegosessionID"
|
||||
} else {
|
||||
SessionName = ar
|
||||
}
|
||||
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64)
|
||||
SessionGCMaxLifetime = int64val
|
||||
} else {
|
||||
SessionGCMaxLifetime = 3600
|
||||
}
|
||||
if ar, err := AppConfig.Bool("sessionon"); err != nil {
|
||||
SessionOn = false
|
||||
} else {
|
||||
SessionOn = ar
|
||||
}
|
||||
if ar := AppConfig.String("sessionprovider"); ar == "" {
|
||||
SessionProvider = "memory"
|
||||
} else {
|
||||
SessionProvider = ar
|
||||
}
|
||||
if ar := AppConfig.String("sessionname"); ar == "" {
|
||||
SessionName = "beegosessionID"
|
||||
} else {
|
||||
SessionName = ar
|
||||
}
|
||||
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64)
|
||||
SessionGCMaxLifetime = int64val
|
||||
} else {
|
||||
SessionGCMaxLifetime = 3600
|
||||
}
|
||||
```
|
||||
在beego.Run函数中增加如下代码:
|
||||
```Go
|
||||
|
||||
if SessionOn {
|
||||
GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime)
|
||||
go GlobalSessions.GC()
|
||||
}
|
||||
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
|
||||
}
|
||||
func (c *Controller) StartSession() (sess session.Session) {
|
||||
sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
return
|
||||
}
|
||||
```
|
||||
## session使用
|
||||
通过上面的代码我们可以看到,beego框架简单地继承了session功能,那么在项目中如何使用呢?
|
||||
@@ -62,28 +62,28 @@ beego中主要有以下的全局变量来控制session处理:
|
||||
首先我们需要在应用的main入口处开启session:
|
||||
```Go
|
||||
|
||||
beego.SessionOn = true
|
||||
beego.SessionOn = true
|
||||
```
|
||||
|
||||
然后我们就可以在控制器的相应方法中如下所示的使用session了:
|
||||
```Go
|
||||
|
||||
func (this *MainController) Get() {
|
||||
var intcount int
|
||||
sess := this.StartSession()
|
||||
count := sess.Get("count")
|
||||
if count == nil {
|
||||
intcount = 0
|
||||
} else {
|
||||
intcount = count.(int)
|
||||
}
|
||||
intcount = intcount + 1
|
||||
sess.Set("count", intcount)
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.Data["Count"] = intcount
|
||||
this.TplNames = "index.tpl"
|
||||
func (this *MainController) Get() {
|
||||
var intcount int
|
||||
sess := this.StartSession()
|
||||
count := sess.Get("count")
|
||||
if count == nil {
|
||||
intcount = 0
|
||||
} else {
|
||||
intcount = count.(int)
|
||||
}
|
||||
intcount = intcount + 1
|
||||
sess.Set("count", intcount)
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.Data["Count"] = intcount
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
```
|
||||
上面的代码展示了如何在控制逻辑中使用session,主要分两个步骤:
|
||||
|
||||
@@ -91,19 +91,19 @@ beego中主要有以下的全局变量来控制session处理:
|
||||
|
||||
```Go
|
||||
|
||||
//获取对象,类似PHP中的session_start()
|
||||
sess := this.StartSession()
|
||||
//获取对象,类似PHP中的session_start()
|
||||
sess := this.StartSession()
|
||||
```
|
||||
|
||||
2. 使用session进行一般的session值操作
|
||||
|
||||
```Go
|
||||
|
||||
//获取session值,类似PHP中的$_SESSION["count"]
|
||||
sess.Get("count")
|
||||
|
||||
//设置session值
|
||||
sess.Set("count", intcount)
|
||||
//获取session值,类似PHP中的$_SESSION["count"]
|
||||
sess.Get("count")
|
||||
|
||||
//设置session值
|
||||
sess.Set("count", intcount)
|
||||
```
|
||||
从上面代码可以看出基于beego框架开发的应用中使用session相当方便,基本上和PHP中调用`session_start()`类似。
|
||||
|
||||
|
||||
50
zh/14.3.md
50
zh/14.3.md
@@ -23,43 +23,43 @@
|
||||
首先定义一个开发Web应用时相对应的struct,一个字段对应一个form元素,通过struct的tag来定义相应的元素信息和验证信息,如下所示:
|
||||
```Go
|
||||
|
||||
type User struct{
|
||||
Username string `form:text,valid:required`
|
||||
Nickname string `form:text,valid:required`
|
||||
Age int `form:text,valid:required|numeric`
|
||||
Email string `form:text,valid:required|valid_email`
|
||||
Introduce string `form:textarea`
|
||||
}
|
||||
type User struct{
|
||||
Username string `form:text,valid:required`
|
||||
Nickname string `form:text,valid:required`
|
||||
Age int `form:text,valid:required|numeric`
|
||||
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"
|
||||
}
|
||||
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>
|
||||
<h1>New Blog Post</h1>
|
||||
<form action="" method="post">
|
||||
{{.form.render()}}
|
||||
</form>
|
||||
```
|
||||
上面我们定义好了整个的第一步,从struct到显示表单的过程,接下来就是用户填写信息,服务器端接收数据然后验证,最后插入数据库。
|
||||
```Go
|
||||
|
||||
func (this *AddController) Post() {
|
||||
var user User
|
||||
form := this.GetInput(&user)
|
||||
if !form.Validates() {
|
||||
return
|
||||
}
|
||||
models.UserInsert(&user)
|
||||
this.Ctx.Redirect(302, "/admin/index")
|
||||
}
|
||||
func (this *AddController) Post() {
|
||||
var user User
|
||||
form := this.GetInput(&user)
|
||||
if !form.Validates() {
|
||||
return
|
||||
}
|
||||
models.UserInsert(&user)
|
||||
this.Ctx.Redirect(302, "/admin/index")
|
||||
}
|
||||
```
|
||||
## 表单类型
|
||||
以下列表列出来了对应的form元素信息:
|
||||
|
||||
375
zh/14.4.md
375
zh/14.4.md
@@ -11,42 +11,42 @@ beego目前没有针对这三种方式进行任何形式的集成,但是可以
|
||||
这两个认证是一些应用采用的比较简单的认证,目前已经有开源的第三方库支持这两个认证:
|
||||
```Go
|
||||
|
||||
github.com/abbot/go-http-auth
|
||||
github.com/abbot/go-http-auth
|
||||
```
|
||||
下面代码演示了如何把这个库引入beego中从而实现认证:
|
||||
```Go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Prepare() {
|
||||
a := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
if username := a.CheckAuth(this.Ctx.Request); username == "" {
|
||||
a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.TplNames = "index.tpl"
|
||||
return ""
|
||||
}
|
||||
|
||||
type MainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *MainController) Prepare() {
|
||||
a := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
if username := a.CheckAuth(this.Ctx.Request); username == "" {
|
||||
a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Data["Username"] = "astaxie"
|
||||
this.Data["Email"] = "astaxie@gmail.com"
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
```
|
||||
上面代码利用了beego的prepare函数,在执行正常逻辑之前调用了认证函数,这样就非常简单的实现了http auth,digest的认证也是同样的原理。
|
||||
|
||||
@@ -54,84 +54,83 @@ beego目前没有针对这三种方式进行任何形式的集成,但是可以
|
||||
oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一个库实现了这个认证,但是是国外实现的,并没有QQ、微博之类的国内应用认证集成:
|
||||
```Go
|
||||
|
||||
github.com/bradrydzewski/go.auth
|
||||
github.com/bradrydzewski/go.auth
|
||||
```
|
||||
下面代码演示了如何把该库引入beego中从而实现oauth的认证,这里以github为例演示:
|
||||
|
||||
1. 添加两条路由
|
||||
```Go
|
||||
|
||||
beego.RegisterController("/auth/login", &controllers.GithubController{})
|
||||
beego.RegisterController("/mainpage", &controllers.PageController{})
|
||||
beego.RegisterController("/auth/login", &controllers.GithubController{})
|
||||
beego.RegisterController("/mainpage", &controllers.PageController{})
|
||||
```
|
||||
2. 然后我们处理GithubController登陆的页面:
|
||||
```Go
|
||||
package controllers
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/bradrydzewski/go.auth"
|
||||
)
|
||||
|
||||
const (
|
||||
githubClientKey = "a0864ea791ce7e7bd0df"
|
||||
githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca"
|
||||
)
|
||||
|
||||
type GithubController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *GithubController) Get() {
|
||||
// set the auth parameters
|
||||
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
|
||||
auth.Config.LoginSuccessRedirect = "/mainpage"
|
||||
auth.Config.CookieSecure = false
|
||||
|
||||
githubHandler := auth.Github(githubClientKey, githubSecretKey)
|
||||
|
||||
githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/bradrydzewski/go.auth"
|
||||
)
|
||||
|
||||
const (
|
||||
githubClientKey = "a0864ea791ce7e7bd0df"
|
||||
githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca"
|
||||
)
|
||||
|
||||
type GithubController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *GithubController) Get() {
|
||||
// set the auth parameters
|
||||
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
|
||||
auth.Config.LoginSuccessRedirect = "/mainpage"
|
||||
auth.Config.CookieSecure = false
|
||||
|
||||
githubHandler := auth.Github(githubClientKey, githubSecretKey)
|
||||
|
||||
githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
|
||||
```
|
||||
3. 处理登陆成功之后的页面
|
||||
```Go
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/bradrydzewski/go.auth"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type PageController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *PageController) Get() {
|
||||
// set the auth parameters
|
||||
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
|
||||
auth.Config.LoginSuccessRedirect = "/mainpage"
|
||||
auth.Config.CookieSecure = false
|
||||
|
||||
user, err := auth.GetUserCookie(this.Ctx.Request)
|
||||
|
||||
//if no active user session then authorize user
|
||||
if err != nil || user.Id() == "" {
|
||||
http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
//else, add the user to the URL and continue
|
||||
this.Ctx.Request.URL.User = url.User(user.Id())
|
||||
this.Data["pic"] = user.Picture()
|
||||
this.Data["id"] = user.Id()
|
||||
this.Data["name"] = user.Name()
|
||||
this.TplNames = "home.tpl"
|
||||
}
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/bradrydzewski/go.auth"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type PageController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *PageController) Get() {
|
||||
// set the auth parameters
|
||||
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
|
||||
auth.Config.LoginSuccessRedirect = "/mainpage"
|
||||
auth.Config.CookieSecure = false
|
||||
|
||||
user, err := auth.GetUserCookie(this.Ctx.Request)
|
||||
|
||||
//if no active user session then authorize user
|
||||
if err != nil || user.Id() == "" {
|
||||
http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
//else, add the user to the URL and continue
|
||||
this.Ctx.Request.URL.User = url.User(user.Id())
|
||||
this.Data["pic"] = user.Picture()
|
||||
this.Data["id"] = user.Id()
|
||||
this.Data["name"] = user.Name()
|
||||
this.TplNames = "home.tpl"
|
||||
}
|
||||
```
|
||||
整个的流程如下,首先打开浏览器输入地址:
|
||||
|
||||
@@ -155,110 +154,110 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一
|
||||
自定义的认证一般都是和session结合验证的,如下代码来源于一个基于beego的开源博客:
|
||||
```Go
|
||||
|
||||
//登陆处理
|
||||
func (this *LoginController) Post() {
|
||||
this.TplNames = "login.tpl"
|
||||
this.Ctx.Request.ParseForm()
|
||||
username := this.Ctx.Request.Form.Get("username")
|
||||
password := this.Ctx.Request.Form.Get("password")
|
||||
md5Password := md5.New()
|
||||
io.WriteString(md5Password, password)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
|
||||
newPass := buffer.String()
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
userInfo := models.GetUserInfo(username)
|
||||
if userInfo.Password == newPass {
|
||||
var users models.User
|
||||
users.Last_logintime = now
|
||||
models.UpdateUserInfo(users)
|
||||
|
||||
//登录成功设置session
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess.Set("uid", userInfo.Id)
|
||||
sess.Set("uname", userInfo.Username)
|
||||
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
//登陆处理
|
||||
func (this *LoginController) Post() {
|
||||
this.TplNames = "login.tpl"
|
||||
this.Ctx.Request.ParseForm()
|
||||
username := this.Ctx.Request.Form.Get("username")
|
||||
password := this.Ctx.Request.Form.Get("password")
|
||||
md5Password := md5.New()
|
||||
io.WriteString(md5Password, password)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
|
||||
newPass := buffer.String()
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
userInfo := models.GetUserInfo(username)
|
||||
if userInfo.Password == newPass {
|
||||
var users models.User
|
||||
users.Last_logintime = now
|
||||
models.UpdateUserInfo(users)
|
||||
|
||||
//登录成功设置session
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess.Set("uid", userInfo.Id)
|
||||
sess.Set("uname", userInfo.Username)
|
||||
|
||||
this.Ctx.Redirect(302, "/")
|
||||
}
|
||||
}
|
||||
|
||||
//注册处理
|
||||
func (this *RegController) Post() {
|
||||
this.TplNames = "reg.tpl"
|
||||
this.Ctx.Request.ParseForm()
|
||||
username := this.Ctx.Request.Form.Get("username")
|
||||
password := this.Ctx.Request.Form.Get("password")
|
||||
usererr := checkUsername(username)
|
||||
fmt.Println(usererr)
|
||||
if usererr == false {
|
||||
this.Data["UsernameErr"] = "Username error, Please to again"
|
||||
return
|
||||
}
|
||||
|
||||
//注册处理
|
||||
func (this *RegController) Post() {
|
||||
this.TplNames = "reg.tpl"
|
||||
this.Ctx.Request.ParseForm()
|
||||
username := this.Ctx.Request.Form.Get("username")
|
||||
password := this.Ctx.Request.Form.Get("password")
|
||||
usererr := checkUsername(username)
|
||||
fmt.Println(usererr)
|
||||
if usererr == false {
|
||||
this.Data["UsernameErr"] = "Username error, Please to again"
|
||||
return
|
||||
}
|
||||
|
||||
passerr := checkPassword(password)
|
||||
if passerr == false {
|
||||
this.Data["PasswordErr"] = "Password error, Please to again"
|
||||
return
|
||||
}
|
||||
|
||||
md5Password := md5.New()
|
||||
io.WriteString(md5Password, password)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
|
||||
newPass := buffer.String()
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
userInfo := models.GetUserInfo(username)
|
||||
|
||||
if userInfo.Username == "" {
|
||||
var users models.User
|
||||
users.Username = username
|
||||
users.Password = newPass
|
||||
users.Created = now
|
||||
users.Last_logintime = now
|
||||
models.AddUser(users)
|
||||
|
||||
//登录成功设置session
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess.Set("uid", userInfo.Id)
|
||||
sess.Set("uname", userInfo.Username)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
} else {
|
||||
this.Data["UsernameErr"] = "User already exists"
|
||||
}
|
||||
|
||||
|
||||
passerr := checkPassword(password)
|
||||
if passerr == false {
|
||||
this.Data["PasswordErr"] = "Password error, Please to again"
|
||||
return
|
||||
}
|
||||
|
||||
func checkPassword(password string) (b bool) {
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
md5Password := md5.New()
|
||||
io.WriteString(md5Password, password)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
|
||||
newPass := buffer.String()
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
userInfo := models.GetUserInfo(username)
|
||||
|
||||
if userInfo.Username == "" {
|
||||
var users models.User
|
||||
users.Username = username
|
||||
users.Password = newPass
|
||||
users.Created = now
|
||||
users.Last_logintime = now
|
||||
models.AddUser(users)
|
||||
|
||||
//登录成功设置session
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess.Set("uid", userInfo.Id)
|
||||
sess.Set("uname", userInfo.Username)
|
||||
this.Ctx.Redirect(302, "/")
|
||||
} else {
|
||||
this.Data["UsernameErr"] = "User already exists"
|
||||
}
|
||||
|
||||
func checkUsername(username string) (b bool) {
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func checkPassword(password string) (b bool) {
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkUsername(username string) (b bool) {
|
||||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
有了用户登陆和注册之后,其他模块的地方可以增加如下这样的用户是否登陆的判断:
|
||||
```Go
|
||||
|
||||
func (this *AddBlogController) Prepare() {
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess_uid := sess.Get("userid")
|
||||
sess_username := sess.Get("username")
|
||||
if sess_uid == nil {
|
||||
this.Ctx.Redirect(302, "/admin/login")
|
||||
return
|
||||
}
|
||||
this.Data["Username"] = sess_username
|
||||
func (this *AddBlogController) Prepare() {
|
||||
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
sess_uid := sess.Get("userid")
|
||||
sess_username := sess.Get("username")
|
||||
if sess_uid == nil {
|
||||
this.Ctx.Redirect(302, "/admin/login")
|
||||
return
|
||||
}
|
||||
this.Data["Username"] = sess_username
|
||||
}
|
||||
```
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
142
zh/14.5.md
142
zh/14.5.md
@@ -6,114 +6,114 @@
|
||||
beego中设置全局变量如下:
|
||||
```Go
|
||||
|
||||
Translation i18n.IL
|
||||
Lang string //设置语言包,zh、en
|
||||
LangPath string //设置语言包所在位置
|
||||
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)
|
||||
}
|
||||
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
|
||||
beegoTplFuncMap["TransMoney"] = i18n.I18nMoney
|
||||
beegoTplFuncMap["Trans"] = i18n.I18nT
|
||||
beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate
|
||||
beegoTplFuncMap["TransMoney"] = i18n.I18nMoney
|
||||
|
||||
func I18nT(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Translate(s)
|
||||
}
|
||||
func I18nT(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Translate(s)
|
||||
}
|
||||
|
||||
func I18nTimeDate(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Time(s)
|
||||
}
|
||||
func I18nTimeDate(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Time(s)
|
||||
}
|
||||
|
||||
func I18nMoney(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Money(s)
|
||||
}
|
||||
func I18nMoney(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return beego.Translation.Money(s)
|
||||
}
|
||||
```
|
||||
## 多语言开发使用
|
||||
1. 设置语言以及语言包所在位置,然后初始化i18n对象:
|
||||
```Go
|
||||
|
||||
beego.Lang = "zh"
|
||||
beego.LangPath = "views/lang"
|
||||
beego.InitLang()
|
||||
beego.Lang = "zh"
|
||||
beego.LangPath = "views/lang"
|
||||
beego.InitLang()
|
||||
```
|
||||
2. 设计多语言包
|
||||
|
||||
上面讲了如何初始化多语言包,现在设计多语言包,多语言包是json文件,如第十章介绍的一样,我们需要把设计的文件放在LangPath下面,例如zh.json或者en.json
|
||||
```json
|
||||
|
||||
# zh.json
|
||||
# zh.json
|
||||
|
||||
{
|
||||
"zh": {
|
||||
"submit": "提交",
|
||||
"create": "创建"
|
||||
}
|
||||
}
|
||||
{
|
||||
"zh": {
|
||||
"submit": "提交",
|
||||
"create": "创建"
|
||||
}
|
||||
}
|
||||
|
||||
#en.json
|
||||
#en.json
|
||||
|
||||
{
|
||||
"en": {
|
||||
"submit": "Submit",
|
||||
"create": "Create"
|
||||
}
|
||||
}
|
||||
{
|
||||
"en": {
|
||||
"submit": "Submit",
|
||||
"create": "Create"
|
||||
}
|
||||
}
|
||||
```
|
||||
3. 使用语言包
|
||||
|
||||
我们可以在controller中调用翻译获取响应的翻译语言,如下所示:
|
||||
```Go
|
||||
|
||||
func (this *MainController) Get() {
|
||||
this.Data["create"] = beego.Translation.Translate("create")
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
func (this *MainController) Get() {
|
||||
this.Data["create"] = beego.Translation.Translate("create")
|
||||
this.TplNames = "index.tpl"
|
||||
}
|
||||
```
|
||||
我们也可以在模板中直接调用响应的翻译函数:
|
||||
```Go
|
||||
|
||||
//直接文本翻译
|
||||
{{.create | Trans}}
|
||||
//直接文本翻译
|
||||
{{.create | Trans}}
|
||||
|
||||
//时间翻译
|
||||
{{.time | TransDate}}
|
||||
//时间翻译
|
||||
{{.time | TransDate}}
|
||||
|
||||
//货币翻译
|
||||
{{.money | TransMoney}}
|
||||
//货币翻译
|
||||
{{.money | TransMoney}}
|
||||
```
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
|
||||
68
zh/14.6.md
68
zh/14.6.md
@@ -2,9 +2,9 @@
|
||||
Go语言有一个非常棒的设计就是标准库里面带有代码的性能监控工具,在两个地方有包:
|
||||
```Go
|
||||
|
||||
net/http/pprof
|
||||
|
||||
runtime/pprof
|
||||
net/http/pprof
|
||||
|
||||
runtime/pprof
|
||||
```
|
||||
其实net/http/pprof中只是使用runtime/pprof包来进行封装了一下,并在http端口上暴露出来
|
||||
|
||||
@@ -14,47 +14,47 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监
|
||||
- 首先在beego.Run函数中根据变量是否自动加载性能包
|
||||
```Go
|
||||
|
||||
if PprofOn {
|
||||
BeeApp.RegisterController(`/debug/pprof`, &ProfController{})
|
||||
BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{})
|
||||
}
|
||||
if PprofOn {
|
||||
BeeApp.RegisterController(`/debug/pprof`, &ProfController{})
|
||||
BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{})
|
||||
}
|
||||
```
|
||||
- 设计ProfConterller
|
||||
```Go
|
||||
|
||||
package beego
|
||||
package beego
|
||||
|
||||
import (
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
type ProfController struct {
|
||||
Controller
|
||||
}
|
||||
|
||||
func (this *ProfController) Get() {
|
||||
switch this.Ctx.Params[":pp"] {
|
||||
default:
|
||||
pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "":
|
||||
pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "cmdline":
|
||||
pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "profile":
|
||||
pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "symbol":
|
||||
pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
this.Ctx.ResponseWriter.WriteHeader(200)
|
||||
}
|
||||
|
||||
import (
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
type ProfController struct {
|
||||
Controller
|
||||
}
|
||||
|
||||
func (this *ProfController) Get() {
|
||||
switch this.Ctx.Params[":pp"] {
|
||||
default:
|
||||
pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "":
|
||||
pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "cmdline":
|
||||
pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "profile":
|
||||
pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
case "symbol":
|
||||
pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request)
|
||||
}
|
||||
this.Ctx.ResponseWriter.WriteHeader(200)
|
||||
}
|
||||
|
||||
```
|
||||
## 使用入门
|
||||
|
||||
通过上面的设计,你可以通过如下代码开启pprof:
|
||||
```Go
|
||||
|
||||
beego.PprofOn = true
|
||||
beego.PprofOn = true
|
||||
```
|
||||
然后你就可以在浏览器中打开如下URL就看到如下界面:
|
||||

|
||||
@@ -70,7 +70,7 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监
|
||||
我们还可以通过命令行获取更多详细的信息
|
||||
```Go
|
||||
|
||||
go tool pprof http://localhost:8080/debug/pprof/profile
|
||||
go tool pprof http://localhost:8080/debug/pprof/profile
|
||||
```
|
||||
这时候程序就会进入30秒的profile收集时间,在这段时间内拼命刷新浏览器上的页面,尽量让cpu占用性能产生数据。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user