Merge commit 'eb5220f7eb5cc01172f309bfaf304a524a567faa' into ja
This commit is contained in:
60
en/README.md
Normal file
60
en/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
***Build Web Application with Golang***
|
||||
======================================
|
||||
|
||||
[Download PDF](https://drive.google.com/file/d/0B2GBHFyTK2N8TzM4dEtIWjBJdEk/edit?usp=sharing)
|
||||
|
||||
### ***Translator Comments***
|
||||
|
||||
This is an English version of [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang), the original version was written by [AstaXie](https://github.com/astaxie) and translated by [Unknown](https://github.com/Unknwon) and [Larry Battle](https://github.com/LarryBattle).
|
||||
|
||||
This book is about how to build web applications in Go. In the first few chapters of the book, the author will review some basic knowledge about Go. However, for an optimal reading experience, you should have a basic understanding of the Go language and the concept of a web application. If you are completely new to programming, this book is not intended to provide sufficient introductory material to get started.
|
||||
|
||||
If anything is unclear due to wording or language issues, feel free to ask me to write a better translation.
|
||||
|
||||
###Acknowledgments for translation help
|
||||
|
||||
- [matalangilbert](https://github.com/matalangilbert)
|
||||
- [nightlyone](https://github.com/nightlyone)
|
||||
- [sbinet](https://github.com/sbinet)
|
||||
- [carbocation](https://github.com/carbocation)
|
||||
- [desimone](https://github.com/desimone)
|
||||
- [reigai](https://github.com/reigai)
|
||||
- [OlingCat](https://github.com/OlingCat)
|
||||
|
||||
### Purpose
|
||||
|
||||
Because I'm interested in web application development, I used my free time to write this book as an open source version. It doesn't mean that I have a very good ability to build web applications; I would like to share what I've done with Go in building web applications.
|
||||
|
||||
- For those of you who are working with PHP/Python/Ruby, you will learn how to build a web application with Go.
|
||||
- For those of you who are working with C/C++, you will know how the web works.
|
||||
|
||||
I believe the purpose of studying is sharing with others. The happiest thing in my life is sharing everything I've known with more people.
|
||||
|
||||
### Donation
|
||||
|
||||
If you like this book, you can ask your Chinese friends to follow this [link](https://me.alipay.com/astaxie) donate the original author, help him write more books with better, more useful, and more interesting content.
|
||||
|
||||
### Exchanging Learning Go
|
||||
|
||||
If you know what is QQ, join the group 259316004. If not, follow this [link](http://download.imqq.com/download.shtml) to get more details. Also, you can join our [forum](http://bbs.beego.me).
|
||||
|
||||
### Acknowledgments
|
||||
|
||||
First, I have to thank the people who are members of Golang-China in QQ group 102319854, they are all very nice and helpful. Then, I need to thank the following people who gave great help when I was writing this book.
|
||||
|
||||
- [四月份平民 April Citizen](https://plus.google.com/110445767383269817959) (review code)
|
||||
- [洪瑞琦 Hong Ruiqi](https://github.com/hongruiqi) (review code)
|
||||
- [边 疆 BianJiang](https://github.com/border) (write the configurations about Vim and Emacs for Go development)
|
||||
- [欧林猫 Oling Cat](https://github.com/OlingCat)(review code)
|
||||
- [吴文磊 Wenlei Wu](mailto:spadesacn@gmail.com)(provide some pictures)
|
||||
- [北极星 Polaris](https://github.com/polaris1119)(review whole book)
|
||||
- [雨 痕 Rain Trail](https://github.com/qyuhen)(review chapter 2 and 3)
|
||||
|
||||
### License
|
||||
|
||||
This book is licensed under the [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/),
|
||||
the code is licensed under a [BSD 3-Clause License](<https://github.com/astaxie/build-web-application-with-golang/blob/master/LICENSE.md>), unless otherwise specified.
|
||||
|
||||
### Get Started
|
||||
|
||||
[Index](./eBook/preface.md)
|
||||
7
en/code/readme.md
Normal file
7
en/code/readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Workspace setup.
|
||||
|
||||
To avoid workspace issues and be able to develop from any folder within this path,
|
||||
set the environment variable `GOPATH` to the path of this directory.
|
||||
|
||||
More info:
|
||||
- [GOPATH documentation](http://golang.org/doc/code.html#GOPATH)
|
||||
14
en/code/src/apps/ch.1.2/main.go
Normal file
14
en/code/src/apps/ch.1.2/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Example code for Chapter 1.2 from "Build Web Application with Golang"
|
||||
// Purpose: Run this file to check if your workspace is setup correctly.
|
||||
// To run, navigate to the current directory in a console and type `go run main.go`
|
||||
// If the text "Hello World" isn't shown, then setup your workspace again.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mymath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
|
||||
}
|
||||
11
en/code/src/apps/ch.2.1/main.go
Normal file
11
en/code/src/apps/ch.2.1/main.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Example code for Chapter ? from "Build Web Application with Golang"
|
||||
// Purpose: Hello world example demonstrating UTF-8 support.
|
||||
// To run in the console, type `go run main.go`
|
||||
// You're missing language fonts, if you're seeing squares or question marks.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n")
|
||||
}
|
||||
277
en/code/src/apps/ch.2.2/main.go
Normal file
277
en/code/src/apps/ch.2.2/main.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Example code for Chapter 2.2 from "Build Web Application with Golang"
|
||||
// Purpose: Goes over the assignment and manipulation of basic data types.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// constants
|
||||
const Pi = 3.1415926
|
||||
|
||||
// booleans default to `false`
|
||||
var isActive bool // global variable
|
||||
var enabled, disabled = true, false // omit type of variables
|
||||
|
||||
// grouped definitions
|
||||
const (
|
||||
i = 1e4
|
||||
MaxThread = 10
|
||||
prefix = "astaxie_"
|
||||
)
|
||||
|
||||
var (
|
||||
frenchHello string // basic form to define string
|
||||
emptyString string = "" // define a string with empty string
|
||||
)
|
||||
|
||||
func show_multiple_assignments() {
|
||||
fmt.Println("show_multiple_assignments()")
|
||||
var v1 int = 42
|
||||
|
||||
// Define three variables with type "int", and initialize their values.
|
||||
// vname1 is v1, vname2 is v2, vname3 is v3
|
||||
var v2, v3 int = 2, 3
|
||||
|
||||
// `:=` only works in functions
|
||||
// `:=` is the short way of declaring variables without
|
||||
// specifying the type and using the keyboard `var`.
|
||||
vname1, vname2, vname3 := v1, v2, v3
|
||||
|
||||
// `_` disregards the returned value.
|
||||
_, b := 34, 35
|
||||
|
||||
fmt.Printf("vname1 = %v, vname2 = %v, vname3 = %v\n", vname1, vname2, vname3)
|
||||
fmt.Printf("v1 = %v, v2 = %v, v3 = %v\n", v1, v2, v3)
|
||||
fmt.Println("b =", b)
|
||||
}
|
||||
func show_bool() {
|
||||
fmt.Println("show_bool()")
|
||||
var available bool // local variable
|
||||
valid := false // Shorthand assignment
|
||||
available = true // assign value to variable
|
||||
|
||||
fmt.Printf("valid = %v, !valid = %v\n", valid, !valid)
|
||||
fmt.Printf("available = %v\n", available)
|
||||
}
|
||||
func show_different_types() {
|
||||
fmt.Println("show_different_types()")
|
||||
var (
|
||||
unicodeChar rune
|
||||
a int8
|
||||
b int16
|
||||
c int32
|
||||
d int64
|
||||
e byte
|
||||
f uint8
|
||||
g int16
|
||||
h uint32
|
||||
i uint64
|
||||
)
|
||||
var cmplx complex64 = 5 + 5i
|
||||
|
||||
fmt.Println("Default values for int types")
|
||||
fmt.Println(unicodeChar, a, b, c, d, e, f, g, h, i)
|
||||
|
||||
fmt.Printf("Value is: %v\n", cmplx)
|
||||
}
|
||||
func show_strings() {
|
||||
fmt.Println("show_strings()")
|
||||
no, yes, maybe := "no", "yes", "maybe" // brief statement
|
||||
japaneseHello := "Ohaiyou"
|
||||
frenchHello = "Bonjour" // basic form of assign values
|
||||
|
||||
fmt.Println("Random strings")
|
||||
fmt.Println(frenchHello, japaneseHello, no, yes, maybe)
|
||||
|
||||
// The backtick, `, will not escape any character in a string
|
||||
fmt.Println(`This
|
||||
is on
|
||||
multiple lines`)
|
||||
}
|
||||
func show_string_manipulation() {
|
||||
fmt.Println("show_string_manipulation()")
|
||||
var s string = "hello"
|
||||
|
||||
//You can't do this with strings
|
||||
//s[0] = 'c'
|
||||
|
||||
s = "hello"
|
||||
c := []byte(s) // convert string to []byte type
|
||||
c[0] = 'c'
|
||||
s2 := string(c) // convert back to string type
|
||||
|
||||
m := " world"
|
||||
a := s + m
|
||||
|
||||
d := "c" + s[1:] // you cannot change string values by index, but you can get values instead.
|
||||
fmt.Printf("%s\n", d)
|
||||
|
||||
fmt.Printf("s = %s, c = %v\n", s, c)
|
||||
fmt.Printf("s2 = %s\n", s2)
|
||||
fmt.Printf("combined strings\na = %s, d = %s\n", a, d)
|
||||
}
|
||||
func show_errors() {
|
||||
fmt.Println("show_errors()")
|
||||
err := errors.New("Example error message\n")
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
}
|
||||
func show_iota() {
|
||||
fmt.Println("show_iota()")
|
||||
const (
|
||||
x = iota // x == 0
|
||||
y = iota // y == 1
|
||||
z = iota // z == 2
|
||||
w // If there is no expression after constants name,
|
||||
// it uses the last expression, so here is saying w = iota implicitly.
|
||||
// Therefore w == 3, and y and x both can omit "= iota" as well.
|
||||
)
|
||||
|
||||
const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0.
|
||||
|
||||
const (
|
||||
e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line.
|
||||
)
|
||||
fmt.Printf("x = %v, y = %v, z = %v, w = %v\n", x, y, z, w)
|
||||
fmt.Printf("v = %v\n", v)
|
||||
fmt.Printf("e = %v, f = %v, g = %v\n", e, f, g)
|
||||
}
|
||||
|
||||
// Functions and variables starting with a capital letter are public to other packages.
|
||||
// Everything else is private.
|
||||
func This_is_public() {}
|
||||
func this_is_private() {}
|
||||
|
||||
func set_default_values() {
|
||||
// default values for the types.
|
||||
const (
|
||||
a int = 0
|
||||
b int8 = 0
|
||||
c int32 = 0
|
||||
d int64 = 0
|
||||
e uint = 0x0
|
||||
f rune = 0 // the actual type of rune is int32
|
||||
g byte = 0x0 // the actual type of byte is uint8
|
||||
h float32 = 0 // length is 4 byte
|
||||
i float64 = 0 //length is 8 byte
|
||||
j bool = false
|
||||
k string = ""
|
||||
)
|
||||
}
|
||||
func show_arrays() {
|
||||
fmt.Println("show_arrays()")
|
||||
var arr [10]int // an array of type int
|
||||
arr[0] = 42 // array is 0-based
|
||||
arr[1] = 13 // assign value to element
|
||||
|
||||
a := [3]int{1, 2, 3} // define a int array with 3 elements
|
||||
|
||||
b := [10]int{1, 2, 3}
|
||||
// define a int array with 10 elements,
|
||||
// and first three are assigned, rest of them use default value 0.
|
||||
|
||||
c := [...]int{4, 5, 6} // use `…` replace with number of length, Go will calculate it for you.
|
||||
|
||||
// define a two-dimensional array with 2 elements, and each element has 4 elements.
|
||||
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
|
||||
|
||||
// You can write about declaration in a shorter way.
|
||||
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
|
||||
|
||||
fmt.Println("arr =", arr)
|
||||
fmt.Printf("The first element is %d\n", arr[0]) // get element value, it returns 42
|
||||
fmt.Printf("The last element is %d\n", arr[9])
|
||||
//it returns default value of 10th element in this array, which is 0 in this case.
|
||||
|
||||
fmt.Println("array a =", a)
|
||||
fmt.Println("array b =", b)
|
||||
fmt.Println("array c =", c)
|
||||
|
||||
fmt.Println("array doubleArray =", doubleArray)
|
||||
fmt.Println("array easyArray =", easyArray)
|
||||
}
|
||||
func show_slices() {
|
||||
fmt.Println("show_slices()")
|
||||
// define a slice with 10 elements which types are byte
|
||||
var ar = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
|
||||
// define two slices with type []byte
|
||||
var a, b []byte
|
||||
|
||||
// a points to elements from 3rd to 5th in array ar.
|
||||
a = ar[2:5]
|
||||
// now a has elements ar[2]、ar[3] and ar[4]
|
||||
|
||||
// b is another slice of array ar
|
||||
b = ar[3:5]
|
||||
// now b has elements ar[3] and ar[4]
|
||||
|
||||
// define an array
|
||||
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
// define two slices
|
||||
var aSlice, bSlice []byte
|
||||
|
||||
// some convenient operations
|
||||
aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c
|
||||
aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j
|
||||
aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements
|
||||
|
||||
// slice from slice
|
||||
aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7
|
||||
bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f
|
||||
bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f
|
||||
bSlice = aSlice[0:5] // slcie could be expanded in range of cap, now bSlice contains d,e,f,g,h
|
||||
bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g
|
||||
|
||||
fmt.Println("slice ar =", ar)
|
||||
fmt.Println("slice a =", a)
|
||||
fmt.Println("slice b =", b)
|
||||
fmt.Println("array =", array)
|
||||
fmt.Println("slice aSlice =", aSlice)
|
||||
fmt.Println("slice bSlice =", bSlice)
|
||||
fmt.Println("len(bSlice) =", len(bSlice))
|
||||
}
|
||||
func show_map() {
|
||||
fmt.Println("show_map()")
|
||||
// use string as key type, int as value type, and you have to use `make` initialize it.
|
||||
var numbers map[string]int
|
||||
// another way to define map
|
||||
numbers = make(map[string]int)
|
||||
numbers["one"] = 1 // assign value by key
|
||||
numbers["ten"] = 10
|
||||
numbers["three"] = 3
|
||||
|
||||
// Initialize a map
|
||||
rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5, "C++": 2}
|
||||
|
||||
fmt.Println("map numbers =", numbers)
|
||||
fmt.Println("The third number is: ", numbers["three"]) // get values
|
||||
// It prints: The third number is: 3
|
||||
|
||||
// map has two return values. For second value, if the key doesn't exist,ok is false,true otherwise.
|
||||
csharpRating, ok := rating["C#"]
|
||||
if ok {
|
||||
fmt.Println("C# is in the map and its rating is ", csharpRating)
|
||||
} else {
|
||||
fmt.Println("We have no rating associated with C# in the map")
|
||||
}
|
||||
|
||||
delete(rating, "C") // delete element with key "c"
|
||||
fmt.Printf("map rating = %#v\n", rating)
|
||||
}
|
||||
func main() {
|
||||
show_multiple_assignments()
|
||||
show_bool()
|
||||
show_different_types()
|
||||
show_strings()
|
||||
show_string_manipulation()
|
||||
show_errors()
|
||||
show_iota()
|
||||
set_default_values()
|
||||
show_arrays()
|
||||
show_slices()
|
||||
show_map()
|
||||
}
|
||||
8
en/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go
Normal file
8
en/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Example code for Chapter 2.2 from "Build Web Application with Golang"
|
||||
// Purpose: Try to fix this program.
|
||||
// From the console, type `go run main.go`
|
||||
package main
|
||||
|
||||
func main() {
|
||||
var i int
|
||||
}
|
||||
26
en/code/src/apps/ch.2.3/basic_functions/main.go
Normal file
26
en/code/src/apps/ch.2.3/basic_functions/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Creating a basic function
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// return greater value between a and b
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
z := 5
|
||||
|
||||
max_xy := max(x, y) // call function max(x, y)
|
||||
max_xz := max(x, z) // call function max(x, z)
|
||||
|
||||
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
|
||||
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
|
||||
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z)) // call function here
|
||||
}
|
||||
14
en/code/src/apps/ch.2.3/hidden_print_methods/main.go
Normal file
14
en/code/src/apps/ch.2.3/hidden_print_methods/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// As of Google go 1.1.2, `println()` and `print()` are hidden functions included from the runtime package.
|
||||
// However it's encouraged to use the print functions from the `fmt` package.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func f() {
|
||||
fmt.Println("First")
|
||||
print("Second ")
|
||||
println(" Third")
|
||||
}
|
||||
func main() {
|
||||
f()
|
||||
}
|
||||
26
en/code/src/apps/ch.2.3/import_packages/main.go
Normal file
26
en/code/src/apps/ch.2.3/import_packages/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows different ways of importing a package.
|
||||
// Note: For the package `only_call_init`, we reference the path from the
|
||||
// base directory of `$GOPATH/src`. The reason being Golang discourage
|
||||
// the use of relative paths when import packages.
|
||||
// BAD: "./only_call_init"
|
||||
// GOOD: "apps/ch.2.3/import_packages/only_call_init"
|
||||
package main
|
||||
|
||||
import (
|
||||
// `_` will only call init() inside the package only_call_init
|
||||
_ "apps/ch.2.3/import_packages/only_call_init"
|
||||
f "fmt" // import the package as `f`
|
||||
. "math" // makes the public methods and constants global
|
||||
"mymath" // custom package located at $GOPATH/src/
|
||||
"os" // normal import of a standard package
|
||||
"text/template" // the package takes the name of last folder path, `template`
|
||||
)
|
||||
|
||||
func main() {
|
||||
f.Println("mymath.Sqrt(4) =", mymath.Sqrt(4))
|
||||
f.Println("E =", E) // references math.E
|
||||
|
||||
t, _ := template.New("test").Parse("Pi^2 = {{.}}")
|
||||
t.Execute(os.Stdout, Pow(Pi, 2))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package only_call_init
|
||||
|
||||
import "fmt"
|
||||
|
||||
func init() {
|
||||
fmt.Println("only_call_init.init() was called.")
|
||||
}
|
||||
142
en/code/src/apps/ch.2.3/main.go
Normal file
142
en/code/src/apps/ch.2.3/main.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Goes over if, else, switch conditions, loops and defer.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func computedValue() int {
|
||||
return 1
|
||||
}
|
||||
func show_if() {
|
||||
fmt.Println("\n#show_if()")
|
||||
x := computedValue()
|
||||
integer := 23
|
||||
|
||||
fmt.Println("x =", x)
|
||||
fmt.Println("integer =", integer)
|
||||
if x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
|
||||
if integer == 3 {
|
||||
fmt.Println("The integer is equal to 3")
|
||||
} else if integer < 3 {
|
||||
fmt.Println("The integer is less than 3")
|
||||
} else {
|
||||
fmt.Println("The integer is greater than 3")
|
||||
}
|
||||
}
|
||||
func show_if_var() {
|
||||
fmt.Println("\n#show_if_var()")
|
||||
// initialize x, then check if x greater than
|
||||
if x := computedValue(); x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
|
||||
// the following code will not compile, since `x` is only accessable with the if/else block
|
||||
// fmt.Println(x)
|
||||
}
|
||||
func show_goto() {
|
||||
fmt.Println("\n#show_goto()")
|
||||
// The call to the label switches the goroutine it seems.
|
||||
i := 0
|
||||
Here: // label ends with ":"
|
||||
fmt.Println(i)
|
||||
i++
|
||||
if i < 10 {
|
||||
goto Here // jump to label "Here"
|
||||
}
|
||||
}
|
||||
func show_for_loop() {
|
||||
fmt.Println("\n#show_for_loop()")
|
||||
sum := 0
|
||||
for index := 0; index < 10; index++ {
|
||||
sum += index
|
||||
}
|
||||
fmt.Println("part 1, sum is equal to ", sum)
|
||||
|
||||
sum = 1
|
||||
// The compiler will remove the `;` from the line below.
|
||||
// for ; sum < 1000 ; {
|
||||
for sum < 1000 {
|
||||
sum += sum
|
||||
}
|
||||
fmt.Println("part 2, sum is equal to ", sum)
|
||||
|
||||
for index := 10; 0 < index; index-- {
|
||||
if index == 5 {
|
||||
break // or continue
|
||||
}
|
||||
fmt.Println(index)
|
||||
}
|
||||
|
||||
}
|
||||
func show_loop_through_map() {
|
||||
fmt.Println("\n#show_loop_through_map()")
|
||||
m := map[string]int{
|
||||
"one": 1,
|
||||
"two": 2,
|
||||
"three": 3,
|
||||
}
|
||||
fmt.Println("map value = ", m)
|
||||
for k, v := range m {
|
||||
fmt.Println("map's key: ", k)
|
||||
fmt.Println("map's value: ", v)
|
||||
}
|
||||
}
|
||||
func show_switch() {
|
||||
fmt.Println("\n#show_switch()")
|
||||
i := 10
|
||||
switch i {
|
||||
case 1:
|
||||
fmt.Println("i is equal to 1")
|
||||
case 2, 3, 4:
|
||||
fmt.Println("i is equal to 2, 3 or 4")
|
||||
case 10:
|
||||
fmt.Println("i is equal to 10")
|
||||
default:
|
||||
fmt.Println("All I know is that i is an integer")
|
||||
}
|
||||
|
||||
integer := 6
|
||||
fmt.Println("integer =", integer)
|
||||
switch integer {
|
||||
case 4:
|
||||
fmt.Println("integer == 4")
|
||||
fallthrough
|
||||
case 5:
|
||||
fmt.Println("integer <= 5")
|
||||
fallthrough
|
||||
case 6:
|
||||
fmt.Println("integer <= 6")
|
||||
fallthrough
|
||||
case 7:
|
||||
fmt.Println("integer <= 7")
|
||||
fallthrough
|
||||
case 8:
|
||||
fmt.Println("integer <= 8")
|
||||
fallthrough
|
||||
default:
|
||||
fmt.Println("default case")
|
||||
}
|
||||
}
|
||||
func show_defer() {
|
||||
fmt.Println("\nshow_defer()")
|
||||
defer fmt.Println("(last defer)")
|
||||
for i := 0; i < 5; i++ {
|
||||
defer fmt.Printf("%d ", i)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
show_if()
|
||||
show_if_var()
|
||||
show_goto()
|
||||
show_for_loop()
|
||||
show_loop_through_map()
|
||||
show_switch()
|
||||
show_defer()
|
||||
}
|
||||
31
en/code/src/apps/ch.2.3/panic_and_recover/main.go
Normal file
31
en/code/src/apps/ch.2.3/panic_and_recover/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Showing how to use `panic()` and `recover()`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var user = os.Getenv("USER")
|
||||
|
||||
func check_user() {
|
||||
if user == "" {
|
||||
panic("no value for $USER")
|
||||
}
|
||||
fmt.Println("Environment Variable `USER` =", user)
|
||||
}
|
||||
func throwsPanic(f func()) (b bool) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
fmt.Println("Panic message =", x);
|
||||
b = true
|
||||
}
|
||||
}()
|
||||
f() // if f causes panic, it will recover
|
||||
return
|
||||
}
|
||||
func main(){
|
||||
didPanic := throwsPanic(check_user)
|
||||
fmt.Println("didPanic =", didPanic)
|
||||
}
|
||||
31
en/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go
Normal file
31
en/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows passing a variable by value and reference
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func add_by_value(a int) int {
|
||||
a = a + 1
|
||||
return a
|
||||
}
|
||||
func add_by_reference(a *int) int {
|
||||
*a = *a + 1
|
||||
return *a
|
||||
}
|
||||
func show_add_by_value() {
|
||||
x := 3
|
||||
fmt.Println("x = ", x)
|
||||
fmt.Println("add_by_value(x) =", add_by_value(x) )
|
||||
fmt.Println("x = ", x)
|
||||
}
|
||||
func show_add_by_reference() {
|
||||
x := 3
|
||||
fmt.Println("x = ", x)
|
||||
// &x pass memory address of x
|
||||
fmt.Println("add_by_reference(&x) =", add_by_reference(&x) )
|
||||
fmt.Println("x = ", x)
|
||||
}
|
||||
func main() {
|
||||
show_add_by_value()
|
||||
show_add_by_reference()
|
||||
}
|
||||
44
en/code/src/apps/ch.2.3/type_function/main.go
Normal file
44
en/code/src/apps/ch.2.3/type_function/main.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to define a function type
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type testInt func(int) bool // define a function type of variable
|
||||
|
||||
func isOdd(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isEven(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pass the function `f` as an argument to another function
|
||||
|
||||
func filter(slice []int, f testInt) []int {
|
||||
var result []int
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
func init() {
|
||||
fmt.Println("\n#init() was called.")
|
||||
}
|
||||
func main() {
|
||||
slice := []int{1, 2, 3, 4, 5, 7}
|
||||
fmt.Println("slice = ", slice)
|
||||
odd := filter(slice, isOdd) // use function as values
|
||||
fmt.Println("Odd elements of slice are: ", odd)
|
||||
even := filter(slice, isEven)
|
||||
fmt.Println("Even elements of slice are: ", even)
|
||||
}
|
||||
20
en/code/src/apps/ch.2.3/variadic_functions/main.go
Normal file
20
en/code/src/apps/ch.2.3/variadic_functions/main.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Example code for Chapter 2.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to return multiple values from a function
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// return results of A + B and A * B
|
||||
func SumAndProduct(A, B int) (int, int) {
|
||||
return A + B, A * B
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
|
||||
xPLUSy, xTIMESy := SumAndProduct(x, y)
|
||||
|
||||
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
|
||||
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
|
||||
}
|
||||
43
en/code/src/apps/ch.2.4/compare_age/main.go
Normal file
43
en/code/src/apps/ch.2.4/compare_age/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Example code for Chapter 2.4 from "Build Web Application with Golang"
|
||||
// Purpose: Shows you how to pass and use structs.
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// define a new type
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
// compare age of two people, return the older person and differences of age
|
||||
// struct is passed by value
|
||||
func Older(p1, p2 person) (person, int) {
|
||||
if p1.age > p2.age {
|
||||
return p1, p1.age - p2.age
|
||||
}
|
||||
return p2, p2.age - p1.age
|
||||
}
|
||||
|
||||
func main() {
|
||||
var tom person
|
||||
|
||||
// initialization
|
||||
tom.name, tom.age = "Tom", 18
|
||||
|
||||
// initialize two values by format "field:value"
|
||||
bob := person{age: 25, name: "Bob"}
|
||||
|
||||
// initialize two values with order
|
||||
paul := person{"Paul", 43}
|
||||
|
||||
tb_Older, tb_diff := Older(tom, bob)
|
||||
tp_Older, tp_diff := Older(tom, paul)
|
||||
bp_Older, bp_diff := Older(bob, paul)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff)
|
||||
}
|
||||
39
en/code/src/apps/ch.2.4/embedded_structs/main.go
Normal file
39
en/code/src/apps/ch.2.4/embedded_structs/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Example code for Chapter 2.4 from "Build Web Application with Golang"
|
||||
// Purpose: Example of embedded fields
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // anonymous field, it means Student struct includes all fields that Human has.
|
||||
speciality string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// initialize a student
|
||||
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
|
||||
|
||||
// access fields
|
||||
fmt.Println("His name is ", mark.name)
|
||||
fmt.Println("His age is ", mark.age)
|
||||
fmt.Println("His weight is ", mark.weight)
|
||||
fmt.Println("His speciality is ", mark.speciality)
|
||||
// modify notes
|
||||
mark.speciality = "AI"
|
||||
fmt.Println("Mark changed his speciality")
|
||||
fmt.Println("His speciality is ", mark.speciality)
|
||||
// modify age
|
||||
fmt.Println("Mark become old")
|
||||
mark.age = 46
|
||||
fmt.Println("His age is", mark.age)
|
||||
// modify weight
|
||||
fmt.Println("Mark is not an athlete any more")
|
||||
mark.weight += 60
|
||||
fmt.Println("His weight is", mark.weight)
|
||||
}
|
||||
39
en/code/src/apps/ch.2.4/embedded_structs2/main.go
Normal file
39
en/code/src/apps/ch.2.4/embedded_structs2/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Example code for Chapter 2.4 from "Build Web Application with Golang"
|
||||
// Purpose: Another example of embedded fields
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Skills []string
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // struct as embedded field
|
||||
Skills // string slice as embedded field
|
||||
int // built-in type as embedded field
|
||||
speciality string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// initialize Student Jane
|
||||
jane := Student{Human: Human{"Jane", 35, 100}, speciality: "Biology"}
|
||||
// access fields
|
||||
fmt.Println("Her name is ", jane.name)
|
||||
fmt.Println("Her age is ", jane.age)
|
||||
fmt.Println("Her weight is ", jane.weight)
|
||||
fmt.Println("Her speciality is ", jane.speciality)
|
||||
// modify value of skill field
|
||||
jane.Skills = []string{"anatomy"}
|
||||
fmt.Println("Her skills are ", jane.Skills)
|
||||
fmt.Println("She acquired two new ones ")
|
||||
jane.Skills = append(jane.Skills, "physics", "golang")
|
||||
fmt.Println("Her skills now are ", jane.Skills)
|
||||
// modify embedded field
|
||||
jane.int = 3
|
||||
fmt.Println("Her preferred number is", jane.int)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Example code for Chapter 2.4 from "Build Web Application with Golang"
|
||||
// Purpose: Shows a name conflict with a embedded field
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string // Human has phone field
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human // embedded field Human
|
||||
speciality string
|
||||
phone string // phone in employee
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
|
||||
fmt.Println("Bob's work phone is:", Bob.phone)
|
||||
// access phone field in Human
|
||||
fmt.Println("Bob's personal phone is:", Bob.Human.phone)
|
||||
}
|
||||
39
en/code/src/apps/ch.2.4/main.go
Normal file
39
en/code/src/apps/ch.2.4/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Example code for Chapter 2.4 from "Build Web Application with Golang"
|
||||
// Purpose: Shows different ways of creating a struct
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func show_basic_struct() {
|
||||
fmt.Println("\nshow_basic_struct()")
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
var P person // p is person type
|
||||
|
||||
P.name = "Astaxie" // assign "Astaxie" to the filed 'name' of p
|
||||
P.age = 25 // assign 25 to field 'age' of p
|
||||
fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p
|
||||
|
||||
tom := person{"Tom", 25}
|
||||
|
||||
bob := person{age: 24, name: "Bob"}
|
||||
|
||||
fmt.Printf("tom = %+v\n", tom)
|
||||
fmt.Printf("bob = %#v\n", bob)
|
||||
}
|
||||
func show_anonymous_struct() {
|
||||
fmt.Println("\nshow_anonymous_struct()")
|
||||
fmt.Printf("Anonymous struct = %#v\n", struct {
|
||||
name string
|
||||
count int
|
||||
}{
|
||||
"counter", 1,
|
||||
})
|
||||
}
|
||||
func main() {
|
||||
show_basic_struct()
|
||||
show_anonymous_struct()
|
||||
}
|
||||
36
en/code/src/apps/ch.2.5/attach_methods_to_struct/main.go
Normal file
36
en/code/src/apps/ch.2.5/attach_methods_to_struct/main.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Example code from Chapter 2.5
|
||||
// Attach method to struct.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
radius float64
|
||||
}
|
||||
|
||||
func (r Rectangle) area() float64 {
|
||||
return r.width * r.height
|
||||
}
|
||||
|
||||
func (c Circle) area() float64 {
|
||||
return c.radius * c.radius * math.Pi
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
c1 := Circle{10}
|
||||
c2 := Circle{25}
|
||||
|
||||
fmt.Println("Area of r1 is: ", r1.area())
|
||||
fmt.Println("Area of r2 is: ", r2.area())
|
||||
fmt.Println("Area of c1 is: ", c1.area())
|
||||
fmt.Println("Area of c2 is: ", c2.area())
|
||||
}
|
||||
73
en/code/src/apps/ch.2.5/box_example/main.go
Normal file
73
en/code/src/apps/ch.2.5/box_example/main.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
WHITE = iota
|
||||
BLACK
|
||||
BLUE
|
||||
RED
|
||||
YELLOW
|
||||
)
|
||||
|
||||
type Color byte
|
||||
|
||||
type Box struct {
|
||||
width, height, depth float64
|
||||
color Color
|
||||
}
|
||||
|
||||
type BoxList []Box //a slice of boxes
|
||||
|
||||
func (b Box) Volume() float64 {
|
||||
return b.width * b.height * b.depth
|
||||
}
|
||||
|
||||
func (b *Box) SetColor(c Color) {
|
||||
b.color = c
|
||||
}
|
||||
|
||||
func (bl BoxList) BiggestsColor() Color {
|
||||
v := 0.00
|
||||
k := Color(WHITE)
|
||||
for _, b := range bl {
|
||||
if b.Volume() > v {
|
||||
v = b.Volume()
|
||||
k = b.color
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (bl BoxList) PaintItBlack() {
|
||||
for i, _ := range bl {
|
||||
bl[i].SetColor(BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) String() string {
|
||||
strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
|
||||
return strings[c]
|
||||
}
|
||||
|
||||
func main() {
|
||||
boxes := BoxList{
|
||||
Box{4, 4, 4, RED},
|
||||
Box{10, 10, 1, YELLOW},
|
||||
Box{1, 1, 20, BLACK},
|
||||
Box{10, 10, 1, BLUE},
|
||||
Box{10, 30, 1, WHITE},
|
||||
Box{20, 20, 20, YELLOW},
|
||||
}
|
||||
|
||||
fmt.Printf("We have %d boxes in our set\n", len(boxes))
|
||||
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
|
||||
fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
|
||||
fmt.Println("Let's paint them all black")
|
||||
boxes.PaintItBlack()
|
||||
fmt.Println("The color of the second one is", boxes[1].color.String())
|
||||
|
||||
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
|
||||
}
|
||||
31
en/code/src/apps/ch.2.5/embedded_method/main.go
Normal file
31
en/code/src/apps/ch.2.5/embedded_method/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // anonymous field
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
}
|
||||
|
||||
// define a method in Human
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
36
en/code/src/apps/ch.2.5/method_overload/main.go
Normal file
36
en/code/src/apps/ch.2.5/method_overload/main.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
}
|
||||
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
18
en/code/src/apps/ch.2.5/pass_struct_to_method/main.go
Normal file
18
en/code/src/apps/ch.2.5/pass_struct_to_method/main.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
func area(r Rectangle) float64 {
|
||||
return r.width * r.height
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
fmt.Println("Area of r1 is: ", area(r1))
|
||||
fmt.Println("Area of r2 is: ", area(r2))
|
||||
}
|
||||
71
en/code/src/apps/ch.2.6/interface/main.go
Normal file
71
en/code/src/apps/ch.2.6/interface/main.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
func (h Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (h Human) Sing(lyrics string) {
|
||||
fmt.Println("La la la la...", lyrics)
|
||||
}
|
||||
|
||||
func (e Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
// Interface Men implemented by Human, Student and Employee
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
|
||||
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
|
||||
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
|
||||
Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
|
||||
|
||||
// define interface i
|
||||
var i Men
|
||||
|
||||
//i can store Student
|
||||
i = mike
|
||||
fmt.Println("This is Mike, a Student:")
|
||||
i.SayHi()
|
||||
i.Sing("November rain")
|
||||
|
||||
//i can store Employee
|
||||
i = Tom
|
||||
fmt.Println("This is Tom, an Employee:")
|
||||
i.SayHi()
|
||||
i.Sing("Born to be wild")
|
||||
|
||||
// slice of Men
|
||||
fmt.Println("Let's use a slice of Men and see what happens")
|
||||
x := make([]Men, 3)
|
||||
// these three elements are different types but they all implemented interface Men
|
||||
x[0], x[1], x[2] = paul, sam, mike
|
||||
|
||||
for _, value := range x{
|
||||
value.SayHi()
|
||||
}
|
||||
}
|
||||
33
en/code/src/apps/ch.2.6/reflection/main.go
Normal file
33
en/code/src/apps/ch.2.6/reflection/main.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func show_interface_none() {
|
||||
fmt.Println("\nshow_interface_none()")
|
||||
var a interface{}
|
||||
a = "string"
|
||||
a = 1
|
||||
a = false
|
||||
fmt.Println("a =", a)
|
||||
}
|
||||
func show_reflection() {
|
||||
fmt.Println("\nshow_reflection()")
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
|
||||
p := reflect.ValueOf(&x)
|
||||
newX := p.Elem()
|
||||
newX.SetFloat(7.1)
|
||||
fmt.Println("newX =", newX)
|
||||
fmt.Println("newX float64() value:", newX.Float())
|
||||
}
|
||||
func main() {
|
||||
show_interface_none()
|
||||
show_reflection()
|
||||
}
|
||||
22
en/code/src/apps/ch.2.6/stringer_interface/main.go
Normal file
22
en/code/src/apps/ch.2.6/stringer_interface/main.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
// Human implemented fmt.Stringer
|
||||
func (h Human) String() string {
|
||||
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
||||
fmt.Println("This Human is : ", Bob)
|
||||
}
|
||||
38
en/code/src/apps/ch.2.6/switch_type_check/main.go
Normal file
38
en/code/src/apps/ch.2.6/switch_type_check/main.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List []Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
func (p Person) String() string {
|
||||
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
||||
}
|
||||
|
||||
func main() {
|
||||
list := make(List, 3)
|
||||
list[0] = 1 //an int
|
||||
list[1] = "Hello" //a string
|
||||
list[2] = Person{"Dennis", 70}
|
||||
|
||||
for index, element := range list {
|
||||
switch value := element.(type) {
|
||||
case int:
|
||||
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
||||
case string:
|
||||
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
||||
case Person:
|
||||
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
||||
default:
|
||||
fmt.Println("list[%d] is of a different type", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
37
en/code/src/apps/ch.2.6/type_check/main.go
Normal file
37
en/code/src/apps/ch.2.6/type_check/main.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List []Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
func (p Person) String() string {
|
||||
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
||||
}
|
||||
|
||||
func main() {
|
||||
list := make(List, 3)
|
||||
list[0] = 1 // an int
|
||||
list[1] = "Hello" // a string
|
||||
list[2] = Person{"Dennis", 70}
|
||||
|
||||
for index, element := range list {
|
||||
if value, ok := element.(int); ok {
|
||||
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
||||
} else if value, ok := element.(string); ok {
|
||||
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
||||
} else if value, ok := element.(Person); ok {
|
||||
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
||||
} else {
|
||||
fmt.Println("list[%d] is of a different type", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
13
en/code/src/apps/ch.2.7/buffered_channel/main.go
Normal file
13
en/code/src/apps/ch.2.7/buffered_channel/main.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to use a buffered channel
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
20
en/code/src/apps/ch.2.7/goroutine/main.go
Normal file
20
en/code/src/apps/ch.2.7/goroutine/main.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to launch a simple gorountine
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world") // create a new goroutine
|
||||
say("hello") // current goroutine
|
||||
}
|
||||
24
en/code/src/apps/ch.2.7/range_and_close_channel/main.go
Normal file
24
en/code/src/apps/ch.2.7/range_and_close_channel/main.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to close and interate through a channel
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x+y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
30
en/code/src/apps/ch.2.7/select_channel/main.go
Normal file
30
en/code/src/apps/ch.2.7/select_channel/main.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to use `select`
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x+y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
27
en/code/src/apps/ch.2.7/timeout/main.go
Normal file
27
en/code/src/apps/ch.2.7/timeout/main.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to create and use a timeout
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <-c:
|
||||
fmt.Println(v)
|
||||
case <-time.After(5 * time.Second):
|
||||
fmt.Println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-o
|
||||
}
|
||||
24
en/code/src/apps/ch.2.7/unbuffered_channel/main.go
Normal file
24
en/code/src/apps/ch.2.7/unbuffered_channel/main.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Example code for Chapter 2.7 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to create and use a unbuffered channel
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x+y)
|
||||
}
|
||||
31
en/code/src/apps/ch.3.2/main.go
Normal file
31
en/code/src/apps/ch.3.2/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Example code for Chapter 3.2 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to acces the form values from the request
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() // parse arguments, you have to call this by yourself
|
||||
fmt.Println(r.Form) // print form information in server side
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") // send data to client side
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) // set router
|
||||
err := http.ListenAndServe(":9090", nil) // set listen port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
30
en/code/src/apps/ch.3.4/main.go
Normal file
30
en/code/src/apps/ch.3.4/main.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Example code for Chapter 3.4 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to create a handler for `http.ListenAndServe()`
|
||||
// Run `go run main.go` then access `http://localhost:9090`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type MyMux struct {
|
||||
}
|
||||
|
||||
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
sayhelloName(w, r)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello myroute!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := &MyMux{}
|
||||
http.ListenAndServe(":9090", mux)
|
||||
}
|
||||
12
en/code/src/apps/ch.4.1/login.gtpl
Normal file
12
en/code/src/apps/ch.4.1/login.gtpl
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
Username:<input type="text" name="username">
|
||||
Password:<input type="password" name="password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
48
en/code/src/apps/ch.4.1/main.go
Normal file
48
en/code/src/apps/ch.4.1/main.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Example code for Chapter 4.1 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to create a simple login using a template
|
||||
// Run: `go run main.go`, then access `http://localhost:9090` and `http://localhost:9090/login`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //Parse url parameters passed, then parse the response packet for the POST body (request body)
|
||||
// attention: If you do not call ParseForm method, the following data can not be obtained form
|
||||
fmt.Println(r.Form) // print information on server side.
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") // write data to response
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //get request method
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, nil)
|
||||
} else {
|
||||
r.ParseForm()
|
||||
// logic part of log in
|
||||
fmt.Println("username:", r.Form["username"])
|
||||
fmt.Println("password:", r.Form["password"])
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) // setting router rule
|
||||
http.HandleFunc("/login", login)
|
||||
err := http.ListenAndServe(":9090", nil) // setting listening port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
59
en/code/src/apps/ch.4.2/main.go
Normal file
59
en/code/src/apps/ch.4.2/main.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Example code for Chapter 4.2 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to perform server-side validation of user input from a form.
|
||||
// Also shows to use multiple template files with predefined template names.
|
||||
// Run `go run main.go` and then access http://localhost:9090
|
||||
package main
|
||||
|
||||
import (
|
||||
"apps/ch.4.2/validator"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
PORT = "9090"
|
||||
HOST_URL = "http://localhost:" + PORT
|
||||
)
|
||||
|
||||
var t *template.Template
|
||||
|
||||
type Links struct {
|
||||
BadLinks [][2]string
|
||||
}
|
||||
|
||||
// invalid links to display for testing.
|
||||
var links Links
|
||||
|
||||
func index(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect)
|
||||
}
|
||||
func profileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
t.ExecuteTemplate(w, "profile", links)
|
||||
}
|
||||
func checkProfile(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
p := validator.ProfilePage{&r.Form}
|
||||
t.ExecuteTemplate(w, "submission", p.GetErrors())
|
||||
}
|
||||
|
||||
// This function is called before main()
|
||||
func init() {
|
||||
// Note: we can reference the loaded templates by their defined name inside the template files.
|
||||
t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl"))
|
||||
|
||||
list := make([][2]string, 2)
|
||||
list[0] = [2]string{HOST_URL + "/checkprofile", "No data"}
|
||||
list[1] = [2]string{HOST_URL + "/checkprofile?age=1&gender=guy&shirtsize=big", "Invalid options"}
|
||||
links = Links{list}
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", index)
|
||||
http.HandleFunc("/profile", profileHandler)
|
||||
http.HandleFunc("/checkprofile", checkProfile)
|
||||
|
||||
err := http.ListenAndServe(":"+PORT, nil) // setting listening port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
89
en/code/src/apps/ch.4.2/profile.gtpl
Normal file
89
en/code/src/apps/ch.4.2/profile.gtpl
Normal file
@@ -0,0 +1,89 @@
|
||||
{{define "profile"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
.row{
|
||||
display: table-row;
|
||||
}
|
||||
.cell{
|
||||
display: table-cell;
|
||||
}
|
||||
.required{
|
||||
color: red
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h2>Profile Setup:</h2>
|
||||
<form action="/checkprofile">
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>User Name:</div>
|
||||
<div class="cell"><input type="text" name="username" id="username" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Age:</div>
|
||||
<div class="cell"><input type="number" min="13" max="130" name="age" id="age" size="3" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Email:</div>
|
||||
<div class="cell"><input type="email" name="email" id="email" placeholder="john@example.com" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Birth day:</div>
|
||||
<div class="cell">
|
||||
<input type="date" name="birthday" id="birthday" placeholder="MM/DD/YYYY" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Gender:</div>
|
||||
<div class="cell">
|
||||
<label for="gender_male">
|
||||
Male: <input type="radio" name="gender" value="m" id="gender_male"/>
|
||||
</label>
|
||||
<label for="gender_female">
|
||||
Female: <input type="radio" name="gender" value="f" id="gender_female"/>
|
||||
</label>
|
||||
<label for="gender_na">
|
||||
N/A: <input type="radio" name="gender" value="na" id="gender_na"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Siblings:</div>
|
||||
<div class="cell">
|
||||
<label for="sibling_male">
|
||||
Brother: <input type="checkbox" name="sibling" value="m" id="sibling_male"/>
|
||||
</label>
|
||||
<label for="sibling_female">
|
||||
Sister: <input type="checkbox" name="sibling" value="f" id="sibling_female"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Shirt Size:</div>
|
||||
<div class="cell">
|
||||
<select id="shirt_size" >
|
||||
<option></option>
|
||||
<option value="s">Small</option>
|
||||
<option value="m">Medium</option>
|
||||
<option value="l">Large</option>
|
||||
<option value="xl">X-Large</option>
|
||||
<option value="xxl">XX-Large</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Chinese Name:</div>
|
||||
<div class="cell"><input type="text" name="chineseName" id="chineseName"/></div>
|
||||
</div>
|
||||
<br/>
|
||||
<span class="required">*</span>Required
|
||||
<br/>
|
||||
<input type="submit" value="Submit" id="submitBtn"/>
|
||||
</form>
|
||||
<h2>Invalid submissions</h2>
|
||||
<ol>{{range .BadLinks}}
|
||||
<li><a href="{{index . 0}}">{{index . 1}}</a></li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
16
en/code/src/apps/ch.4.2/submission.gtpl
Normal file
16
en/code/src/apps/ch.4.2/submission.gtpl
Normal file
@@ -0,0 +1,16 @@
|
||||
{{define "submission"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
{{if .Errors}}
|
||||
<h2>Errors:</h2>
|
||||
<ol>
|
||||
{{range .Errors}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{else}}
|
||||
Profile successfully submitted.
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
175
en/code/src/apps/ch.4.2/validator/main.go
Normal file
175
en/code/src/apps/ch.4.2/validator/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// This file contains all the validators to validate the profile page.
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProfilePage struct {
|
||||
Form *url.Values
|
||||
}
|
||||
type Errors struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// Goes through the form object and validates each element.
|
||||
// Attachs an error to the output if validation fails.
|
||||
func (p *ProfilePage) GetErrors() Errors {
|
||||
errs := make([]error, 0, 10)
|
||||
if *p.Form == nil || len(*p.Form) < 1 {
|
||||
errs = append(errs, errors.New("No data was received. Please submit from the profile page."))
|
||||
}
|
||||
for name, val := range *p.Form {
|
||||
if fn, ok := stringValidator[name]; ok {
|
||||
if err := fn(strings.Join(val, "")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
if fn, ok := stringsValidator[name]; ok {
|
||||
if err := fn(val); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Errors{errs}
|
||||
}
|
||||
|
||||
const (
|
||||
// Used for parsing the time
|
||||
mmddyyyyForm = "01/02/2006" // we want the date sent in this format
|
||||
yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format
|
||||
)
|
||||
|
||||
var stringValidator map[string]func(string) error = map[string]func(string) error{
|
||||
// parameter name : validator reference
|
||||
"age": checkAge,
|
||||
"birthday": checkDate,
|
||||
"chineseName": checkChineseName,
|
||||
"email": checkEmail,
|
||||
"gender": checkGender,
|
||||
"shirtsize": checkShirtSize,
|
||||
"username": checkUsername,
|
||||
}
|
||||
var stringsValidator map[string]func([]string) error = map[string]func([]string) error{
|
||||
// parameter name : validator reference
|
||||
"sibling": checkSibling,
|
||||
}
|
||||
|
||||
// Returns true if slices have a common element
|
||||
func doSlicesIntersect(s1, s2 []string) bool {
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
for _, str := range s1 {
|
||||
if isElementInSlice(str, s2) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func isElementInSlice(str string, sl []string) bool {
|
||||
if sl == nil || str == "" {
|
||||
return false
|
||||
}
|
||||
for _, v := range sl {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if all the characters are chinese characters. Won't check if empty.'
|
||||
func checkChineseName(str string) error {
|
||||
if str != "" {
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m {
|
||||
return errors.New("Please make sure that the chinese name only contains chinese characters.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a user name exist.
|
||||
func checkUsername(str string) error {
|
||||
if strings.Trim(str, " ") == "" {
|
||||
return errors.New("Please enter a username.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if age is a number and between 13 and 130
|
||||
func checkAge(str string) error {
|
||||
age, err := strconv.Atoi(str)
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid age.")
|
||||
}
|
||||
if age < 13 {
|
||||
return errors.New("You must be at least 13 years of age to submit.")
|
||||
}
|
||||
if age > 130 {
|
||||
return errors.New("You're too old to register, grandpa.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func checkEmail(str string) error {
|
||||
if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m {
|
||||
fmt.Println("err = ", err)
|
||||
return errors.New("Please enter a valid email address.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a valid date was passed.
|
||||
func checkDate(str string) error {
|
||||
_, err := time.Parse(mmddyyyyForm, str)
|
||||
if err != nil {
|
||||
_, err = time.Parse(yyyymmddForm, str)
|
||||
}
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid Date.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the passed input is a known gender option
|
||||
func checkGender(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f", "na"}
|
||||
if !isElementInSlice(str, siblings) {
|
||||
return errors.New("Please select a valid gender.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if all the values are known options.
|
||||
func checkSibling(strs []string) error {
|
||||
if strs == nil || len(strs) < 1 {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f"}
|
||||
if siblings != nil && !doSlicesIntersect(siblings, strs) {
|
||||
return errors.New("Please select a valid sibling")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the shirt size is a known option.
|
||||
func checkShirtSize(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
shirts := []string{"s", "m", "l", "xl", "xxl"}
|
||||
if !isElementInSlice(str, shirts) {
|
||||
return errors.New("Please select a valid shirt size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
28
en/code/src/apps/ch.4.3/index.gtpl
Normal file
28
en/code/src/apps/ch.4.3/index.gtpl
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h2>Cross Site Scripting Attack Test</h2>
|
||||
{{if .}}
|
||||
Previous User Input: <br/>
|
||||
|
||||
<code><pre>{{.}}</pre></code>
|
||||
{{end}}
|
||||
<form action="/">
|
||||
<label>
|
||||
User Input:
|
||||
<input type="text" size=50 name="userinput" id="userinput"/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Escape Input:
|
||||
<input type="checkbox" value="1" name="escape" id="escape"/>
|
||||
</label>
|
||||
<br/>
|
||||
<input type="submit" id="submitBtn" value="Submit"/>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
var s = "<scri"+"pt>alert('pOwned by XSS.')</scri"+"pt>"
|
||||
document.getElementById("userinput").value = s;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
39
en/code/src/apps/ch.4.3/main.go
Normal file
39
en/code/src/apps/ch.4.3/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Example code for Chapter 4.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to properly escape input
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
textTemplate "text/template"
|
||||
)
|
||||
|
||||
var t *template.Template = template.Must(template.ParseFiles("index.gtpl"))
|
||||
|
||||
func index(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
userInput := r.Form.Get("userinput")
|
||||
if 0 < len(r.Form.Get("escape")) {
|
||||
t.Execute(w, template.HTMLEscapeString(userInput))
|
||||
} else {
|
||||
// Variables with type `template.HTML` are not escaped when passed to `.Execute()`
|
||||
t.Execute(w, template.HTML(userInput))
|
||||
}
|
||||
}
|
||||
func templateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
userInput := r.Form.Get("userinput")
|
||||
if 0 < len(r.Form.Get("escape")) {
|
||||
// `html/template.Execute()` escapes input
|
||||
t.Execute(w, userInput)
|
||||
} else {
|
||||
tt := textTemplate.Must(textTemplate.ParseFiles("index.gtpl"))
|
||||
// `text/template.Execute()` doesn't escape input
|
||||
tt.Execute(w, userInput)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", index)
|
||||
http.HandleFunc("/template", templateHandler)
|
||||
http.ListenAndServe(":9090", nil)
|
||||
}
|
||||
54
en/code/src/apps/ch.4.4/main.go
Normal file
54
en/code/src/apps/ch.4.4/main.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Example code for Chapter 3.2 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to prevent duplicate submissions by using tokens
|
||||
// Example code for Chapter 4.4 based off the code from Chapter 4.2
|
||||
// Run `go run main.go` then access http://localhost:9090
|
||||
package main
|
||||
|
||||
import (
|
||||
"apps/ch.4.4/nonce"
|
||||
"apps/ch.4.4/validator"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
PORT = "9090"
|
||||
HOST_URL = "http://localhost:" + PORT
|
||||
)
|
||||
|
||||
var submissions nonce.Nonces
|
||||
var t *template.Template
|
||||
|
||||
func index(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect)
|
||||
}
|
||||
func profileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
t.ExecuteTemplate(w, "profile", submissions.NewNonce())
|
||||
}
|
||||
func checkProfile(w http.ResponseWriter, r *http.Request) {
|
||||
var errs validator.Errors
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if err := submissions.CheckThenMarkToken(token); err != nil {
|
||||
errs = validator.Errors{[]error{err}}
|
||||
} else {
|
||||
p := validator.ProfilePage{&r.Form}
|
||||
errs = p.GetErrors()
|
||||
}
|
||||
t.ExecuteTemplate(w, "submission", errs)
|
||||
}
|
||||
func init() {
|
||||
submissions = nonce.New()
|
||||
t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl"))
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", index)
|
||||
http.HandleFunc("/profile", profileHandler)
|
||||
http.HandleFunc("/checkprofile", checkProfile)
|
||||
|
||||
err := http.ListenAndServe(":"+PORT, nil) // setting listening port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
70
en/code/src/apps/ch.4.4/nonce/main.go
Normal file
70
en/code/src/apps/ch.4.4/nonce/main.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// A nonce is a number or string used only once.
|
||||
// This is useful for generating a unique token for login pages to prevent duplicate submissions.
|
||||
package nonce
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Contains a unique token
|
||||
type Nonce struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// Keeps track of marked/used tokens
|
||||
type Nonces struct {
|
||||
hashs map[string]bool
|
||||
}
|
||||
|
||||
func New() Nonces {
|
||||
return Nonces{make(map[string]bool)}
|
||||
}
|
||||
func (n *Nonces) NewNonce() Nonce {
|
||||
return Nonce{n.NewToken()}
|
||||
}
|
||||
|
||||
// Returns a new unique token
|
||||
func (n *Nonces) NewToken() string {
|
||||
t := createToken()
|
||||
for n.HasToken(t) {
|
||||
t = createToken()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Checks if token has been marked.
|
||||
func (n *Nonces) HasToken(token string) bool {
|
||||
return n.hashs[token] == true
|
||||
}
|
||||
func (n *Nonces) MarkToken(token string) {
|
||||
n.hashs[token] = true
|
||||
}
|
||||
func (n *Nonces) CheckToken(token string) error {
|
||||
if token == "" {
|
||||
return errors.New("No token supplied")
|
||||
}
|
||||
if n.HasToken(token) {
|
||||
return errors.New("Duplicate submission.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (n *Nonces) CheckThenMarkToken(token string) error {
|
||||
defer n.MarkToken(token)
|
||||
if err := n.CheckToken(token); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func createToken() string {
|
||||
h := md5.New()
|
||||
now := time.Now().Unix()
|
||||
io.WriteString(h, strconv.FormatInt(now, 10))
|
||||
io.WriteString(h, strconv.FormatInt(rand.Int63(), 10))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
85
en/code/src/apps/ch.4.4/profile.gtpl
Normal file
85
en/code/src/apps/ch.4.4/profile.gtpl
Normal file
@@ -0,0 +1,85 @@
|
||||
{{define "profile"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
.row{
|
||||
display: table-row;
|
||||
}
|
||||
.cell{
|
||||
display: table-cell;
|
||||
}
|
||||
.required{
|
||||
color: red
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h2>Profile Setup:</h2>
|
||||
<form action="/checkprofile" method="POST">
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>User Name:</div>
|
||||
<div class="cell"><input type="text" name="username" id="username" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Age:</div>
|
||||
<div class="cell"><input type="number" min="13" max="130" name="age" id="age" size="3" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Email:</div>
|
||||
<div class="cell"><input type="email" name="email" id="email" placeholder="john@example.com" required/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell"><span class="required">*</span>Birth day:</div>
|
||||
<div class="cell">
|
||||
<input type="date" name="birthday" id="birthday" placeholder="MM/DD/YYYY" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Gender:</div>
|
||||
<div class="cell">
|
||||
<label for="gender_male">
|
||||
Male: <input type="radio" name="gender" value="m" id="gender_male"/>
|
||||
</label>
|
||||
<label for="gender_female">
|
||||
Female: <input type="radio" name="gender" value="f" id="gender_female"/>
|
||||
</label>
|
||||
<label for="gender_na">
|
||||
N/A: <input type="radio" name="gender" value="na" id="gender_na"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Siblings:</div>
|
||||
<div class="cell">
|
||||
<label for="sibling_male">
|
||||
Brother: <input type="checkbox" name="sibling" value="m" id="sibling_male"/>
|
||||
</label>
|
||||
<label for="sibling_female">
|
||||
Sister: <input type="checkbox" name="sibling" value="f" id="sibling_female"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Shirt Size:</div>
|
||||
<div class="cell">
|
||||
<select id="shirt_size" >
|
||||
<option></option>
|
||||
<option value="s">Small</option>
|
||||
<option value="m">Medium</option>
|
||||
<option value="l">Large</option>
|
||||
<option value="xl">X-Large</option>
|
||||
<option value="xxl">XX-Large</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell">Chinese Name:</div>
|
||||
<div class="cell"><input type="text" name="chineseName" id="chineseName"/></div>
|
||||
</div>
|
||||
<br/>
|
||||
<span class="required">*</span>Required
|
||||
<br/>
|
||||
<input type="hidden" name="token" value="{{.Token}}"/>
|
||||
<input type="submit" value="Submit" id="submitBtn"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
17
en/code/src/apps/ch.4.4/submission.gtpl
Normal file
17
en/code/src/apps/ch.4.4/submission.gtpl
Normal file
@@ -0,0 +1,17 @@
|
||||
{{define "submission"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
{{if .Errors}}
|
||||
<h2>Errors:</h2>
|
||||
<ol>
|
||||
{{range .Errors}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{else}}
|
||||
Profile successfully submitted.<br/>
|
||||
Note: Refreshing the page will produce a duplicate entry.
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
175
en/code/src/apps/ch.4.4/validator/main.go
Normal file
175
en/code/src/apps/ch.4.4/validator/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// This file contains all the validators to validate the profile page.
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProfilePage struct {
|
||||
Form *url.Values
|
||||
}
|
||||
type Errors struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// Goes through the form object and validates each element.
|
||||
// Attachs an error to the output if validation fails.
|
||||
func (p *ProfilePage) GetErrors() Errors {
|
||||
errs := make([]error, 0, 10)
|
||||
if *p.Form == nil || len(*p.Form) < 1 {
|
||||
errs = append(errs, errors.New("No data was received. Please submit from the profile page."))
|
||||
}
|
||||
for name, val := range *p.Form {
|
||||
if fn, ok := stringValidator[name]; ok {
|
||||
if err := fn(strings.Join(val, "")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
if fn, ok := stringsValidator[name]; ok {
|
||||
if err := fn(val); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Errors{errs}
|
||||
}
|
||||
|
||||
const (
|
||||
// Used for parsing the time
|
||||
mmddyyyyForm = "01/02/2006" // we want the date sent in this format
|
||||
yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format
|
||||
)
|
||||
|
||||
var stringValidator map[string]func(string) error = map[string]func(string) error{
|
||||
// parameter name : validator reference
|
||||
"age": checkAge,
|
||||
"birthday": checkDate,
|
||||
"chineseName": checkChineseName,
|
||||
"email": checkEmail,
|
||||
"gender": checkGender,
|
||||
"shirtsize": checkShirtSize,
|
||||
"username": checkUsername,
|
||||
}
|
||||
var stringsValidator map[string]func([]string) error = map[string]func([]string) error{
|
||||
// parameter name : validator reference
|
||||
"sibling": checkSibling,
|
||||
}
|
||||
|
||||
// Returns true if slices have a common element
|
||||
func doSlicesIntersect(s1, s2 []string) bool {
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
for _, str := range s1 {
|
||||
if isElementInSlice(str, s2) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func isElementInSlice(str string, sl []string) bool {
|
||||
if sl == nil || str == "" {
|
||||
return false
|
||||
}
|
||||
for _, v := range sl {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if all the characters are chinese characters. Won't check if empty.'
|
||||
func checkChineseName(str string) error {
|
||||
if str != "" {
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m {
|
||||
return errors.New("Please make sure that the chinese name only contains chinese characters.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a user name exist.
|
||||
func checkUsername(str string) error {
|
||||
if strings.Trim(str, " ") == "" {
|
||||
return errors.New("Please enter a username.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if age is a number and between 13 and 130
|
||||
func checkAge(str string) error {
|
||||
age, err := strconv.Atoi(str)
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid age.")
|
||||
}
|
||||
if age < 13 {
|
||||
return errors.New("You must be at least 13 years of age to submit.")
|
||||
}
|
||||
if age > 130 {
|
||||
return errors.New("You're too old to register, grandpa.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func checkEmail(str string) error {
|
||||
if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m {
|
||||
fmt.Println("err = ", err)
|
||||
return errors.New("Please enter a valid email address.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a valid date was passed.
|
||||
func checkDate(str string) error {
|
||||
_, err := time.Parse(mmddyyyyForm, str)
|
||||
if err != nil {
|
||||
_, err = time.Parse(yyyymmddForm, str)
|
||||
}
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid Date.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the passed input is a known gender option
|
||||
func checkGender(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f", "na"}
|
||||
if !isElementInSlice(str, siblings) {
|
||||
return errors.New("Please select a valid gender.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if all the values are known options.
|
||||
func checkSibling(strs []string) error {
|
||||
if strs == nil || len(strs) < 1 {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f"}
|
||||
if siblings != nil && !doSlicesIntersect(siblings, strs) {
|
||||
return errors.New("Please select a valid sibling")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the shirt size is a known option.
|
||||
func checkShirtSize(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
shirts := []string{"s", "m", "l", "xl", "xxl"}
|
||||
if !isElementInSlice(str, shirts) {
|
||||
return errors.New("Please select a valid shirt size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
46
en/code/src/apps/ch.4.5/client_upload/main.go
Normal file
46
en/code/src/apps/ch.4.5/client_upload/main.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func postFile(filename string, targetUrl string) {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||||
checkError(err)
|
||||
|
||||
fh, err := os.Open(filename)
|
||||
checkError(err)
|
||||
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
checkError(err)
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||||
checkError(err)
|
||||
|
||||
defer resp.Body.Close()
|
||||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||||
checkError(err)
|
||||
|
||||
fmt.Println(resp.Status)
|
||||
fmt.Println(string(resp_body))
|
||||
}
|
||||
func main() {
|
||||
target_url := "http://localhost:9090/upload"
|
||||
filename := "../file.txt"
|
||||
postFile(filename, target_url)
|
||||
}
|
||||
15
en/code/src/apps/ch.4.5/index.gtpl
Normal file
15
en/code/src/apps/ch.4.5/index.gtpl
Normal file
@@ -0,0 +1,15 @@
|
||||
{{define "index"}}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Upload file</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
|
||||
<input type="file" name="uploadfile" />
|
||||
<input type="hidden" name="token" value="{{.}}"/>
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
64
en/code/src/apps/ch.4.5/main.go
Normal file
64
en/code/src/apps/ch.4.5/main.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Example code for Chapter 4.5
|
||||
// Purpose is to create a server to handle uploading files.
|
||||
package main
|
||||
|
||||
import (
|
||||
"apps/ch.4.4/nonce"
|
||||
"apps/ch.4.4/validator"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
const MiB_UNIT = 1 << 20
|
||||
|
||||
var t *template.Template
|
||||
var submissions nonce.Nonces = nonce.New()
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := t.ExecuteTemplate(w, "index", submissions.NewToken())
|
||||
checkError(err)
|
||||
}
|
||||
func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var errs validator.Errors
|
||||
r.ParseMultipartForm(32 * MiB_UNIT)
|
||||
token := r.Form.Get("token")
|
||||
if err := submissions.CheckThenMarkToken(token); err != nil {
|
||||
errs = validator.Errors{[]error{err}}
|
||||
} else {
|
||||
file, handler, err := r.FormFile("uploadfile")
|
||||
checkError(err)
|
||||
saveUpload(file, handler)
|
||||
}
|
||||
err := t.ExecuteTemplate(w, "upload", errs)
|
||||
checkError(err)
|
||||
}
|
||||
func saveUpload(file multipart.File, handler *multipart.FileHeader) {
|
||||
defer file.Close()
|
||||
fmt.Printf("Uploaded file info: %#v", handler.Header)
|
||||
localFilename := fmt.Sprintf("./uploads/%v.%v", handler.Filename, submissions.NewToken())
|
||||
f, err := os.OpenFile(localFilename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
checkError(err)
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, file)
|
||||
checkError(err)
|
||||
}
|
||||
func init() {
|
||||
var err error
|
||||
t, err = template.ParseFiles("index.gtpl", "upload.gtpl")
|
||||
checkError(err)
|
||||
}
|
||||
func main() {
|
||||
http.HandleFunc("/", indexHandler)
|
||||
http.HandleFunc("/upload", uploadHandler)
|
||||
err := http.ListenAndServe(":9090", nil)
|
||||
checkError(err)
|
||||
}
|
||||
70
en/code/src/apps/ch.4.5/nonce/main.go
Normal file
70
en/code/src/apps/ch.4.5/nonce/main.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// A nonce is a number or string used only once.
|
||||
// This is useful for generating a unique token for login pages to prevent duplicate submissions.
|
||||
package nonce
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Contains a unique token
|
||||
type Nonce struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// Keeps track of marked/used tokens
|
||||
type Nonces struct {
|
||||
hashs map[string]bool
|
||||
}
|
||||
|
||||
func New() Nonces {
|
||||
return Nonces{make(map[string]bool)}
|
||||
}
|
||||
func (n *Nonces) NewNonce() Nonce {
|
||||
return Nonce{n.NewToken()}
|
||||
}
|
||||
|
||||
// Returns a new unique token
|
||||
func (n *Nonces) NewToken() string {
|
||||
t := createToken()
|
||||
for n.HasToken(t) {
|
||||
t = createToken()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Checks if token has been marked.
|
||||
func (n *Nonces) HasToken(token string) bool {
|
||||
return n.hashs[token] == true
|
||||
}
|
||||
func (n *Nonces) MarkToken(token string) {
|
||||
n.hashs[token] = true
|
||||
}
|
||||
func (n *Nonces) CheckToken(token string) error {
|
||||
if token == "" {
|
||||
return errors.New("No token supplied")
|
||||
}
|
||||
if n.HasToken(token) {
|
||||
return errors.New("Duplicate submission.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (n *Nonces) CheckThenMarkToken(token string) error {
|
||||
defer n.MarkToken(token)
|
||||
if err := n.CheckToken(token); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func createToken() string {
|
||||
h := md5.New()
|
||||
now := time.Now().Unix()
|
||||
io.WriteString(h, strconv.FormatInt(now, 10))
|
||||
io.WriteString(h, strconv.FormatInt(rand.Int63(), 10))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
17
en/code/src/apps/ch.4.5/upload.gtpl
Normal file
17
en/code/src/apps/ch.4.5/upload.gtpl
Normal file
@@ -0,0 +1,17 @@
|
||||
{{define "upload"}}<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
{{if .Errors}}
|
||||
<h2>Errors:</h2>
|
||||
<ol>
|
||||
{{range .Errors}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{else}}
|
||||
File uploaded successfully.<br/>
|
||||
Note: Refreshing the page will produce a duplicate entry.
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
175
en/code/src/apps/ch.4.5/validator/main.go
Normal file
175
en/code/src/apps/ch.4.5/validator/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// This file contains all the validators to validate the profile page.
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProfilePage struct {
|
||||
Form *url.Values
|
||||
}
|
||||
type Errors struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// Goes through the form object and validates each element.
|
||||
// Attachs an error to the output if validation fails.
|
||||
func (p *ProfilePage) GetErrors() Errors {
|
||||
errs := make([]error, 0, 10)
|
||||
if *p.Form == nil || len(*p.Form) < 1 {
|
||||
errs = append(errs, errors.New("No data was received. Please submit from the profile page."))
|
||||
}
|
||||
for name, val := range *p.Form {
|
||||
if fn, ok := stringValidator[name]; ok {
|
||||
if err := fn(strings.Join(val, "")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else {
|
||||
if fn, ok := stringsValidator[name]; ok {
|
||||
if err := fn(val); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Errors{errs}
|
||||
}
|
||||
|
||||
const (
|
||||
// Used for parsing the time
|
||||
mmddyyyyForm = "01/02/2006" // we want the date sent in this format
|
||||
yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format
|
||||
)
|
||||
|
||||
var stringValidator map[string]func(string) error = map[string]func(string) error{
|
||||
// parameter name : validator reference
|
||||
"age": checkAge,
|
||||
"birthday": checkDate,
|
||||
"chineseName": checkChineseName,
|
||||
"email": checkEmail,
|
||||
"gender": checkGender,
|
||||
"shirtsize": checkShirtSize,
|
||||
"username": checkUsername,
|
||||
}
|
||||
var stringsValidator map[string]func([]string) error = map[string]func([]string) error{
|
||||
// parameter name : validator reference
|
||||
"sibling": checkSibling,
|
||||
}
|
||||
|
||||
// Returns true if slices have a common element
|
||||
func doSlicesIntersect(s1, s2 []string) bool {
|
||||
if s1 == nil || s2 == nil {
|
||||
return false
|
||||
}
|
||||
for _, str := range s1 {
|
||||
if isElementInSlice(str, s2) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func isElementInSlice(str string, sl []string) bool {
|
||||
if sl == nil || str == "" {
|
||||
return false
|
||||
}
|
||||
for _, v := range sl {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if all the characters are chinese characters. Won't check if empty.'
|
||||
func checkChineseName(str string) error {
|
||||
if str != "" {
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m {
|
||||
return errors.New("Please make sure that the chinese name only contains chinese characters.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a user name exist.
|
||||
func checkUsername(str string) error {
|
||||
if strings.Trim(str, " ") == "" {
|
||||
return errors.New("Please enter a username.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if age is a number and between 13 and 130
|
||||
func checkAge(str string) error {
|
||||
age, err := strconv.Atoi(str)
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid age.")
|
||||
}
|
||||
if age < 13 {
|
||||
return errors.New("You must be at least 13 years of age to submit.")
|
||||
}
|
||||
if age > 130 {
|
||||
return errors.New("You're too old to register, grandpa.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func checkEmail(str string) error {
|
||||
if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m {
|
||||
fmt.Println("err = ", err)
|
||||
return errors.New("Please enter a valid email address.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if a valid date was passed.
|
||||
func checkDate(str string) error {
|
||||
_, err := time.Parse(mmddyyyyForm, str)
|
||||
if err != nil {
|
||||
_, err = time.Parse(yyyymmddForm, str)
|
||||
}
|
||||
if str == "" || err != nil {
|
||||
return errors.New("Please enter a valid Date.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the passed input is a known gender option
|
||||
func checkGender(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f", "na"}
|
||||
if !isElementInSlice(str, siblings) {
|
||||
return errors.New("Please select a valid gender.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if all the values are known options.
|
||||
func checkSibling(strs []string) error {
|
||||
if strs == nil || len(strs) < 1 {
|
||||
return nil
|
||||
}
|
||||
siblings := []string{"m", "f"}
|
||||
if siblings != nil && !doSlicesIntersect(siblings, strs) {
|
||||
return errors.New("Please select a valid sibling")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if the shirt size is a known option.
|
||||
func checkShirtSize(str string) error {
|
||||
if str == "" {
|
||||
return nil
|
||||
}
|
||||
shirts := []string{"s", "m", "l", "xl", "xxl"}
|
||||
if !isElementInSlice(str, shirts) {
|
||||
return errors.New("Please select a valid shirt size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
76
en/code/src/apps/ch.5.2/main.go
Normal file
76
en/code/src/apps/ch.5.2/main.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Example code for Chapter 5.2 from "Build Web Application with Golang"
|
||||
// Purpose: Use SQL driver to perform simple CRUD operations.
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
const (
|
||||
DB_USER = "user"
|
||||
DB_PASSWORD = ""
|
||||
DB_NAME = "test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbSouce := fmt.Sprintf("%v:%v@/%v?charset=utf8", DB_USER, DB_PASSWORD, DB_NAME)
|
||||
db, err := sql.Open("mysql", dbSouce)
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("Inserting")
|
||||
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "software developement", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println("id of last inserted row =", id)
|
||||
fmt.Println("Updating")
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
|
||||
fmt.Println("Querying")
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username, department, created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println("uid | username | department | created")
|
||||
fmt.Printf("%3v | %6v | %6v | %6v\n", uid, username, department, created)
|
||||
}
|
||||
|
||||
fmt.Println("Deleting")
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
12
en/code/src/apps/ch.5.2/readme.md
Normal file
12
en/code/src/apps/ch.5.2/readme.md
Normal file
@@ -0,0 +1,12 @@
|
||||
##Setup for `ch.5.2`
|
||||
|
||||
- Step 1) Install and run MySql
|
||||
- Step 2) Create a user and database according to the constants in `main.go`
|
||||
|
||||
DB_USER = "user"
|
||||
DB_PASSWORD = ""
|
||||
DB_NAME = "test"
|
||||
|
||||
- Step 3) Create table `userinfo` located at `schema.sql`
|
||||
- Step 4) Run `go get` to download and install the remote packages.
|
||||
- Step 5) Execute the program with `go run main.go`
|
||||
7
en/code/src/apps/ch.5.2/schema.sql
Normal file
7
en/code/src/apps/ch.5.2/schema.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`departname` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`created` DATE NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
BIN
en/code/src/apps/ch.5.3/foo.db
Normal file
BIN
en/code/src/apps/ch.5.3/foo.db
Normal file
Binary file not shown.
72
en/code/src/apps/ch.5.3/main.go
Normal file
72
en/code/src/apps/ch.5.3/main.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Example code for Chapter 5.3 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to run simple CRUD operations using a sqlite driver
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DB_PATH = "./foo.db"
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("sqlite3", DB_PATH)
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("Inserting")
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "software developement", time.Now().Format("2006-01-02"))
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println("id of last inserted row =", id)
|
||||
fmt.Println("Updating")
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
|
||||
fmt.Println("Querying")
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username, department, created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println("uid | username | department | created")
|
||||
fmt.Printf("%3v | %6v | %8v | %6v\n", uid, username, department, created)
|
||||
}
|
||||
|
||||
fmt.Println("Deleting")
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
23
en/code/src/apps/ch.5.3/readme.md
Normal file
23
en/code/src/apps/ch.5.3/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
## Set up for `ch.5.3`
|
||||
|
||||
- Step 1) Download and install sqlite 3.
|
||||
- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`.
|
||||
- Step 3) Create the `userinfo` table in sqlite using `schema.sql`.
|
||||
|
||||
Read and run sql statements
|
||||
|
||||
sqlite> .read schema.sql
|
||||
|
||||
Show tables
|
||||
|
||||
sqlite> .tables
|
||||
userinfo
|
||||
|
||||
|
||||
- Step 4) Exit sqlite.
|
||||
|
||||
sqlite> .exit
|
||||
|
||||
- Step 5) Run `go get` to download and install remote packages.
|
||||
- Step 6) Run the program with `go run main.go`
|
||||
|
||||
6
en/code/src/apps/ch.5.3/schema.sql
Normal file
6
en/code/src/apps/ch.5.3/schema.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`username` VARCHAR(64) NULL,
|
||||
`department` VARCHAR(64) NULL,
|
||||
`created` DATE NULL
|
||||
);
|
||||
77
en/code/src/apps/ch.5.4/main.go
Normal file
77
en/code/src/apps/ch.5.4/main.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Example code for Chapter 5.4 from "Build Web Application with Golang"
|
||||
// Purpose: Show how to perform CRUD operations using a postgres driver
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/bmizerany/pq"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DB_USER = "user"
|
||||
DB_PASSWORD = ""
|
||||
DB_NAME = "test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable",
|
||||
DB_USER, DB_PASSWORD, DB_NAME)
|
||||
db, err := sql.Open("postgres", dbinfo)
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("# Inserting values")
|
||||
|
||||
var lastInsertId int
|
||||
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;",
|
||||
"astaxie", "software developement", "2012-12-09").Scan(&lastInsertId)
|
||||
checkErr(err)
|
||||
fmt.Println("id of last inserted row =", lastInsertId)
|
||||
|
||||
fmt.Println("# Updating")
|
||||
stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxieupdate", lastInsertId)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
|
||||
fmt.Println("# Querying")
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created time.Time
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println("uid | username | department | created ")
|
||||
fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created)
|
||||
}
|
||||
|
||||
fmt.Println("# Deleting")
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=$1")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(lastInsertId)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "row(s) changed")
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
12
en/code/src/apps/ch.5.4/readme.md
Normal file
12
en/code/src/apps/ch.5.4/readme.md
Normal file
@@ -0,0 +1,12 @@
|
||||
##Setup for ch.5.4
|
||||
|
||||
- Step 1) Install and run Postgres
|
||||
- Step 2) Create a user and database according to the constants in `main.go`
|
||||
|
||||
DB_USER = "user"
|
||||
DB_PASSWORD = ""
|
||||
DB_NAME = "test"
|
||||
|
||||
- Step 3) Create table `userinfo` located at `schema.sql`
|
||||
- Step 4) Run `go get` to download and install the remote packages.
|
||||
- Step 5) Execute the program with `go run main.go`
|
||||
9
en/code/src/apps/ch.5.4/schema.sql
Normal file
9
en/code/src/apps/ch.5.4/schema.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE userinfo
|
||||
(
|
||||
uid serial NOT NULL,
|
||||
username character varying(100) NOT NULL,
|
||||
departname character varying(500) NOT NULL,
|
||||
Created date,
|
||||
CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
|
||||
)
|
||||
WITH (OIDS=FALSE);
|
||||
170
en/code/src/apps/ch.5.5/main.go
Normal file
170
en/code/src/apps/ch.5.5/main.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Example code for Chapter 5.5
|
||||
// Purpose is to show to use BeeDB ORM for basic CRUD operations for sqlite3
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/astaxie/beedb"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"time"
|
||||
)
|
||||
|
||||
var orm beedb.Model
|
||||
|
||||
type Userinfo struct {
|
||||
Uid int `beedb:"PK"`
|
||||
Username string
|
||||
Department string
|
||||
Created string
|
||||
}
|
||||
|
||||
const DB_PATH = "./foo.db"
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func getTimeStamp() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
func insertUsingStruct() int64 {
|
||||
fmt.Println("insertUsingStruct()")
|
||||
var obj Userinfo
|
||||
obj.Username = "Test Add User"
|
||||
obj.Department = "Test Add Department"
|
||||
obj.Created = getTimeStamp()
|
||||
checkError(orm.Save(&obj))
|
||||
fmt.Printf("%+v\n", obj)
|
||||
return int64(obj.Uid)
|
||||
}
|
||||
func insertUsingMap() int64 {
|
||||
fmt.Println("insertUsingMap()")
|
||||
add := make(map[string]interface{})
|
||||
add["username"] = "astaxie"
|
||||
add["department"] = "cloud develop"
|
||||
add["created"] = getTimeStamp()
|
||||
id, err := orm.SetTable("userinfo").Insert(add)
|
||||
checkError(err)
|
||||
fmt.Println("Last row inserted id =", id)
|
||||
return id
|
||||
}
|
||||
|
||||
func getOneUserInfo(id int64) Userinfo {
|
||||
fmt.Println("getOneUserInfo()")
|
||||
var obj Userinfo
|
||||
checkError(orm.Where("uid=?", id).Find(&obj))
|
||||
return obj
|
||||
}
|
||||
|
||||
func getAllUserInfo(id int64) []Userinfo {
|
||||
fmt.Println("getAllUserInfo()")
|
||||
var alluser []Userinfo
|
||||
checkError(orm.Limit(10).Where("uid>?", id).FindAll(&alluser))
|
||||
return alluser
|
||||
}
|
||||
|
||||
func updateUserinfo(id int64) {
|
||||
fmt.Println("updateUserinfo()")
|
||||
var obj Userinfo
|
||||
obj.Uid = int(id)
|
||||
obj.Username = "Update Username"
|
||||
obj.Department = "Update Department"
|
||||
obj.Created = getTimeStamp()
|
||||
checkError(orm.Save(&obj))
|
||||
fmt.Printf("%+v\n", obj)
|
||||
}
|
||||
|
||||
func updateUsingMap(id int64) {
|
||||
fmt.Println("updateUsingMap()")
|
||||
t := make(map[string]interface{})
|
||||
t["username"] = "updateastaxie"
|
||||
//update one
|
||||
// id, err := orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t)
|
||||
//update batch
|
||||
lastId, err := orm.SetTable("userinfo").Where("uid>?", id).Update(t)
|
||||
checkError(err)
|
||||
fmt.Println("Last row updated id =", lastId)
|
||||
}
|
||||
|
||||
func getMapsFromSelect(id int64) []map[string][]byte {
|
||||
fmt.Println("getMapsFromSelect()")
|
||||
//Original SQL Backinfo resultsSlice []map[string][]byte
|
||||
//default PrimaryKey id
|
||||
c, err := orm.SetTable("userinfo").SetPK("uid").Where(id).Select("uid,username").FindMap()
|
||||
checkError(err)
|
||||
fmt.Printf("%+v\n", c)
|
||||
return c
|
||||
}
|
||||
|
||||
func groupby() {
|
||||
fmt.Println("groupby()")
|
||||
//Original SQL Group By
|
||||
b, err := orm.SetTable("userinfo").GroupBy("username").Having("username='updateastaxie'").FindMap()
|
||||
checkError(err)
|
||||
fmt.Printf("%+v\n", b)
|
||||
}
|
||||
|
||||
func joinTables(id int64) {
|
||||
fmt.Println("joinTables()")
|
||||
//Original SQL Join Table
|
||||
a, err := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid").Where("userinfo.uid=?", id).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap()
|
||||
checkError(err)
|
||||
fmt.Printf("%+v\n", a)
|
||||
}
|
||||
|
||||
func deleteWithUserinfo(id int64) {
|
||||
fmt.Println("deleteWithUserinfo()")
|
||||
obj := getOneUserInfo(id)
|
||||
id, err := orm.Delete(&obj)
|
||||
checkError(err)
|
||||
fmt.Println("Last row deleted id =", id)
|
||||
}
|
||||
|
||||
func deleteRows() {
|
||||
fmt.Println("deleteRows()")
|
||||
//original SQL delete
|
||||
id, err := orm.SetTable("userinfo").Where("uid>?", 2).DeleteRow()
|
||||
checkError(err)
|
||||
fmt.Println("Last row updated id =", id)
|
||||
}
|
||||
|
||||
func deleteAllUserinfo(id int64) {
|
||||
fmt.Println("deleteAllUserinfo()")
|
||||
//delete all data
|
||||
alluser := getAllUserInfo(id)
|
||||
id, err := orm.DeleteAll(&alluser)
|
||||
checkError(err)
|
||||
fmt.Println("Last row updated id =", id)
|
||||
}
|
||||
func main() {
|
||||
db, err := sql.Open("sqlite3", DB_PATH)
|
||||
checkError(err)
|
||||
orm = beedb.New(db)
|
||||
var lastIdInserted int64
|
||||
|
||||
fmt.Println("Inserting")
|
||||
lastIdInserted = insertUsingStruct()
|
||||
insertUsingMap()
|
||||
|
||||
a := getOneUserInfo(lastIdInserted)
|
||||
fmt.Println(a)
|
||||
|
||||
b := getAllUserInfo(lastIdInserted)
|
||||
fmt.Println(b)
|
||||
|
||||
fmt.Println("Updating")
|
||||
updateUserinfo(lastIdInserted)
|
||||
updateUsingMap(lastIdInserted)
|
||||
|
||||
fmt.Println("Querying")
|
||||
getMapsFromSelect(lastIdInserted)
|
||||
groupby()
|
||||
joinTables(lastIdInserted)
|
||||
|
||||
fmt.Println("Deleting")
|
||||
deleteWithUserinfo(lastIdInserted)
|
||||
deleteRows()
|
||||
deleteAllUserinfo(lastIdInserted)
|
||||
}
|
||||
23
en/code/src/apps/ch.5.5/readme.md
Normal file
23
en/code/src/apps/ch.5.5/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
## Set up for `ch.5.5`
|
||||
|
||||
- Step 1) Download and install sqlite 3.
|
||||
- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`.
|
||||
- Step 3) Create the tables found in `schema.sql` in sqlite.
|
||||
|
||||
Read and run sql statements
|
||||
|
||||
sqlite> .read schema.sql
|
||||
|
||||
Show tables
|
||||
|
||||
sqlite> .tables
|
||||
userinfo
|
||||
userdetail
|
||||
|
||||
- Step 4) Exit sqlite.
|
||||
|
||||
sqlite> .exit
|
||||
|
||||
- Step 5) Run `go get` to download and install remote packages.
|
||||
- Step 6) Run the program with `go run main.go`
|
||||
|
||||
12
en/code/src/apps/ch.5.5/schema.sql
Normal file
12
en/code/src/apps/ch.5.5/schema.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`username` VARCHAR(64) NULL,
|
||||
`department` VARCHAR(64) NULL,
|
||||
`created` DATE NULL
|
||||
);
|
||||
CREATE TABLE `userdetail` (
|
||||
`uid` INT(10) NULL,
|
||||
`intro` TEXT NULL,
|
||||
`profile` TEXT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
58
en/code/src/apps/ch.5.6/mongodb/main.go
Normal file
58
en/code/src/apps/ch.5.6/mongodb/main.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Example code for Chapter 5.6 from "Build Web Application with Golang"
|
||||
// Purpose: Shows you have to perform basic CRUD operations for a mongodb driver.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"labix.org/v2/mgo"
|
||||
"labix.org/v2/mgo/bson"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Phone string
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
DB_NAME = "test"
|
||||
DB_COLLECTION = "people"
|
||||
)
|
||||
|
||||
func main() {
|
||||
session, err := mgo.Dial("localhost")
|
||||
checkError(err)
|
||||
defer session.Close()
|
||||
|
||||
session.SetMode(mgo.Monotonic, true)
|
||||
|
||||
c := session.DB(DB_NAME).C(DB_COLLECTION)
|
||||
err = c.DropCollection()
|
||||
checkError(err)
|
||||
|
||||
ale := Person{"Ale", "555-5555"}
|
||||
cla := Person{"Cla", "555-1234"}
|
||||
|
||||
fmt.Println("Inserting")
|
||||
err = c.Insert(&ale, &cla)
|
||||
checkError(err)
|
||||
|
||||
fmt.Println("Updating")
|
||||
ale.Phone = "555-0101"
|
||||
err = c.Update(bson.M{"name": "Ale"}, &ale)
|
||||
|
||||
fmt.Println("Querying")
|
||||
result := Person{}
|
||||
err = c.Find(bson.M{"name": "Ale"}).One(&result)
|
||||
checkError(err)
|
||||
fmt.Println("Phone:", result.Phone)
|
||||
|
||||
fmt.Println("Deleting")
|
||||
err = c.Remove(bson.M{"name": "Ale"})
|
||||
checkError(err)
|
||||
}
|
||||
6
en/code/src/apps/ch.5.6/mongodb/readme.md
Normal file
6
en/code/src/apps/ch.5.6/mongodb/readme.md
Normal file
@@ -0,0 +1,6 @@
|
||||
##Setup for `ch.5.6` for MongoDB
|
||||
|
||||
- Step 1) Install and run MongoDB
|
||||
- Step 2) Launch the MongoDB daemon (mongod) to start the server.
|
||||
- Step 3) Run `go get` to download and install the remote packages.
|
||||
- Step 4) Execute the program with `go run main.go`
|
||||
60
en/code/src/apps/ch.5.6/redis/main.go
Normal file
60
en/code/src/apps/ch.5.6/redis/main.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Example code for Chapter 5.6 from "Build Web Application with Golang"
|
||||
// Purpose: Shows you have to perform basic CRUD operations for a redis driver.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/goredis"
|
||||
)
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
DB_PORT = "9191"
|
||||
DB_URL = "127.0.0.1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var client goredis.Client
|
||||
|
||||
// Set the default port in Redis
|
||||
client.Addr = DB_URL + ":" + DB_PORT
|
||||
|
||||
// string manipulation
|
||||
fmt.Println("Inserting")
|
||||
err := client.Set("a", []byte("hello"))
|
||||
checkError(err)
|
||||
|
||||
// list operation
|
||||
vals := []string{"a", "b", "c", "d"}
|
||||
for _, v := range vals {
|
||||
err = client.Rpush("l", []byte(v))
|
||||
checkError(err)
|
||||
}
|
||||
fmt.Println("Updating")
|
||||
err = client.Set("a", []byte("a is for apple"))
|
||||
checkError(err)
|
||||
err = client.Rpush("l", []byte("e"))
|
||||
checkError(err)
|
||||
|
||||
fmt.Println("Querying")
|
||||
val, err := client.Get("a")
|
||||
checkError(err)
|
||||
fmt.Println(string(val))
|
||||
|
||||
dbvals, err := client.Lrange("l", 0, 4)
|
||||
checkError(err)
|
||||
for i, v := range dbvals {
|
||||
println(i, ":", string(v))
|
||||
}
|
||||
|
||||
fmt.Println("Deleting")
|
||||
_, err = client.Del("l")
|
||||
checkError(err)
|
||||
_, err = client.Del("a")
|
||||
checkError(err)
|
||||
}
|
||||
10
en/code/src/apps/ch.5.6/redis/readme.md
Normal file
10
en/code/src/apps/ch.5.6/redis/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
##Setup for `ch.5.6` for Redis
|
||||
|
||||
- Step 1) Install and run Redis
|
||||
- Step 2) Launch the Redis server matching the DB constants.
|
||||
|
||||
DB_PORT = "9191"
|
||||
DB_URL = "127.0.0.1"
|
||||
|
||||
- Step 3) Run `go get` to download and install the remote packages.
|
||||
- Step 4) Execute the program with `go run main.go`
|
||||
12
en/code/src/mymath/sqrt.go
Normal file
12
en/code/src/mymath/sqrt.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Example code for Chapter 1.2 from "Build Web Application with Golang"
|
||||
// Purpose: Shows how to create a simple package called `mymath`
|
||||
// This package must be imported from another go file to run.
|
||||
package mymath
|
||||
|
||||
func Sqrt(x float64) float64 {
|
||||
z := 0.0
|
||||
for i := 0; i < 1000; i++ {
|
||||
z -= (z*z - x) / (2 * x)
|
||||
}
|
||||
return z
|
||||
}
|
||||
20
en/eBook/01.0.md
Normal file
20
en/eBook/01.0.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 1 Go Environment Configuration
|
||||
|
||||
Welcome to the world of Go, let's start exploring!
|
||||
|
||||
Go is a fast-compiled, garbage-collected, concurrent systems programming language. It has the following advantages:
|
||||
|
||||
- Compiles a large project within a few seconds.
|
||||
- Provides a software development model that is easy to reason about, avoiding most of the problems that caused by C-style header files.
|
||||
- Is a static language that does not have levels in its type system, so users do not need to spend much time dealing with relations between types. It is more like a lightweight object-oriented language.
|
||||
- Performs garbage collection. It provides basic support for concurrency and communication.
|
||||
- Designed for multi-core computers.
|
||||
|
||||
Go is a compiled language. It combines the development efficiency of interpreted or dynamic languages with the security of static languages. It is going to be the language of choice for the modern multi-core computers with networking. For these purposes, there are some problems that have to be resolved in language itself, such as a richly expressive lightweight type system, concurrency, and strictly regulated garbage collection. For quite some time, no packages or tools have come out that have solved all of these problems pragmatically; thus was born the motivation for the Go language.
|
||||
|
||||
In this chapter, I will show you how to install and configure your own Go development environment.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Next section: [Installation](01.1.md)
|
||||
122
en/eBook/01.1.md
Normal file
122
en/eBook/01.1.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# 1.1 Installation
|
||||
|
||||
## Three ways to install Go
|
||||
|
||||
There are many ways to configure the Go development environment on your computer, you can choose any one you like. The three most common ways are as follows.
|
||||
|
||||
|
||||
- Official installation packages.
|
||||
- The Go team provides convenient installation packages in Windows, Linux, Mac and other operating systems. The easiest way to get started.
|
||||
- Install from source code.
|
||||
- Popular with developers who are familiar with Unix-like systems.
|
||||
- Use third-party tools.
|
||||
- There are many third-party tools and package managers for installing Go, like apt-get in Ubuntu and homebrew for Mac.
|
||||
|
||||
In case you want to install more than one version of Go in one computer, you should take a look at the tool called [GVM](https://github.com/moovweb/gvm). It is the best tool I've seen so far for achieving this job, otherwise you have to know how to deal with this problem by yourself.
|
||||
|
||||
## Install from source code
|
||||
|
||||
Because some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step.
|
||||
|
||||
On a Mac, once you install the Xcode, you have already have the compiler.
|
||||
|
||||
On Unix-like systems, you need to install gcc or a like compiler. For example, using the package manager apt-get included with Ubuntu, one can install the required compilers as follows:
|
||||
`sudo apt-get install gcc libc6-dev`
|
||||
|
||||
On Windows, you need to install MinGW in order to install gcc. Don't forget to configure environment variables after the installation is finished.( ***Everything looks like this means it's commented by translator: If you are using 64-bit Windows, you would better install 64-bit version of MinGW*** )
|
||||
|
||||
The Go team uses [Mercurial](http://mercurial.selenic.com/downloads/) to manage source code, so you need to install this tool in order to download Go source code.
|
||||
|
||||
At this point, execute following commands to clone Go source code, and start compiling.( ***It will clone source code to you current directory, switch your work path before you continue. This may take some time.*** )
|
||||
|
||||
hg clone -u release https://code.google.com/p/go
|
||||
cd go/src
|
||||
./all.bash
|
||||
|
||||
A successful installation will end with the message "ALL TESTS PASSED."
|
||||
|
||||
On Windows, you can achieve the same by running `all.bat`.
|
||||
|
||||
If you are using Windows, installation package will set environment variables automatically. In Unix-like systems, you need to set these variables manually as follows.( ***If your Go version is greater than 1.0, you don't have to set $GOBIN, and it will automatically be related to your $GOROOT/bin, which we will talk about in the next section***)
|
||||
|
||||
export GOROOT=$HOME/go
|
||||
export GOBIN=$GOROOT/bin
|
||||
export PATH=$PATH:$GOROOT/bin
|
||||
|
||||
If you see the following information on your screen, you're all set.
|
||||
|
||||

|
||||
|
||||
Figure 1.1 Information after installed from source code
|
||||
|
||||
Once you see the usage information of Go, it means you successfully installed Go on your computer. If it says "no such command", check if your $PATH environment variable contains the installation path of Go.
|
||||
|
||||
## Use standard installation packages
|
||||
|
||||
Go has one-click installation packages for every operating system. These packages will install Go in `/usr/local/go` (`c:\Go` in Windows) as default. Of course you can change it, but you also need to change all the environment variables manually as I showed above.
|
||||
|
||||
### How to check if your operating system is 32-bit or 64-bit?
|
||||
|
||||
Our next step depends on your operating system type, so we have to check it before we download the standard installation packages.
|
||||
|
||||
If you are using Windows, press `Win+R` and then run the command tool, type command `systeminfo` and it will show you some useful information just few seconds. Find the line with "system type", if you see "x64-based PC" that means your operating system is 64-bit, 32-bit otherwise.
|
||||
|
||||
I strongly recommend downloading the 64-bit version of package if you are Mac users as Go is no longer supports pure 32-bit processors in Mac OS.
|
||||
|
||||
Linux users can type `uname -a` in the terminal to see system information.
|
||||
64-bit operating system shows as follows.
|
||||
|
||||
<some description> x86_64 x86_64 x86_64 GNU/Linux
|
||||
// some machines such as Ubuntu 10.04 will show as following
|
||||
x86_64 GNU/Linux
|
||||
|
||||
32-bit operating system shows as follows.
|
||||
|
||||
<some description> i686 i686 i386 GNU/Linux
|
||||
|
||||
### Mac
|
||||
|
||||
Go to the [download page](http://code.google.com/p/go/downloads/list), choose `go1.0.3.darwin-386.pkg` for 32-bit systems and `go1.0.3.darwin-amd64.pkg` for 64-bit systems. All the way to the end by clicking "next", `~/go/bin` will be added to $PATH after you finished the installation. Now open the terminal and type `go`. You should now see the what is displayed in figure 1.1.
|
||||
|
||||
### Linux
|
||||
|
||||
Go to the [download page]((http://code.google.com/p/go/downloads/list), choose `go1.0.3.linux-386.tar.gz` for 32-bit systems and `go1.0.3.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in path `$GO_INSTALL_DIR`, uncompress `tar.gz` to the path by command `tar zxvf go1.0.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the what is displayed in figure 1.1.
|
||||
|
||||
### Windows
|
||||
|
||||
Go to the [download page]((http://code.google.com/p/go/downloads/list), choose `go1.0.3.windows-386.msi` for 32-bit systems and `go1.0.3.windows-amd64.msi` for 64-bit systems. All the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the what is displayed in figure 1.1.
|
||||
|
||||
## Use third-party tools
|
||||
|
||||
### GVM
|
||||
|
||||
GVM is a Go multi-version control tool developed by third-party, like rvm in ruby. It's quite easy to use it. Install gvm by typing following commands in your terminal.
|
||||
|
||||
bash < <(curl -s https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer)
|
||||
|
||||
Then we install Go by following commands.
|
||||
|
||||
gvm install go1.0.3
|
||||
gvm use go1.0.3
|
||||
|
||||
After the process finished, you're all set.
|
||||
|
||||
### apt-get
|
||||
|
||||
Ubuntu is the most popular desktop release version of Linux. It uses `apt-get` to manage packages. We can install Go using the following commands.
|
||||
|
||||
sudo add-apt-repository ppa:gophers/go
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-stable
|
||||
|
||||
### Homebrew
|
||||
|
||||
Homebrew is a software manage tool commonly used in Mac to manage software. Just type following commands to install Go.
|
||||
|
||||
brew install go
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Go environment configuration](01.0.md)
|
||||
- Next section: [$GOPATH and workspace](01.2.md)
|
||||
151
en/eBook/01.2.md
Normal file
151
en/eBook/01.2.md
Normal file
@@ -0,0 +1,151 @@
|
||||
#1.2 $GOPATH and workspace
|
||||
|
||||
## $GOPATH
|
||||
|
||||
Go commands all rely on one important environment variable which is called $GOPATH. Notice that this is not the $GOROOT where Go is installed. This variable points to the workspace of Go in your computer. (I use this path in my computer, if you don't have the same directory structure, please replace by yourself.)
|
||||
|
||||
In Unix-like systems, the variable should be used like this.
|
||||
|
||||
export GOPATH=/home/apple/mygo
|
||||
|
||||
In Windows, you need to create a new environment variable called GOPATH, then set its value to `c:\mygo`( ***This value depends on where your workspace is located*** )
|
||||
|
||||
It is OK to have more than one path(workspace) in $GOPATH, but remember that you have to use `:`(`;` in Windows) to break up them. At this point, `go get` will save the content to your first path in $GOPATH.
|
||||
|
||||
In $GOPATH, you must have three folders as follows.
|
||||
|
||||
- `src` for source files whose suffix is .go, .c, .g, .s.
|
||||
- `pkg` for compiled files whose suffix is .a.
|
||||
- `bin` for executable files
|
||||
|
||||
In this book, I use `mygo` as my only path in $GOPATH.
|
||||
|
||||
## Package directory
|
||||
|
||||
Create package source files and folders like `$GOPATH/src/mymath/sqrt.go` (`mymath` is the package name) ( ***Author uses `mymath` as his package name, and same name for the folder where contains package source files***)
|
||||
|
||||
Every time you create a package, you should create a new folder in the `src` directory, folders' name usually as same as the package's that you are going to use. You can have multi-level directory if you want to. For example, you create directories `$GOPATH/src/github.com/astaxie/beedb`, then the package path is `github.com/astaxie/beedb`. The package name will be the last directory in your path, which is `beedb` in this case.
|
||||
|
||||
Execute following commands. ( ***Now author goes back to talk examples*** )
|
||||
|
||||
cd $GOPATH/src
|
||||
mkdir mymath
|
||||
|
||||
Create a new file called `sqrt.go`, type following content to your file.
|
||||
|
||||
// Source code of $GOPATH/src/mymath/sqrt.go
|
||||
package mymath
|
||||
|
||||
func Sqrt(x float64) float64 {
|
||||
z := 0.0
|
||||
for i := 0; i < 1000; i++ {
|
||||
z -= (z*z - x) / (2 * x)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
Now my package directory is created and code work is done. I recommend you to keep same name for your package and the folder contains package source files.
|
||||
|
||||
## Compile packages
|
||||
|
||||
We've already created our package above, but how to compile it for practical? There are two ways to do it.
|
||||
|
||||
1. Switch your work path to the directory of your package, then execute command `go install`.
|
||||
2. Execute above command with file name like `go install mymath`.
|
||||
|
||||
After compiled, we can open the following folder.
|
||||
|
||||
cd $GOPATH/pkg/${GOOS}_${GOARCH}
|
||||
// you can see the file was generated
|
||||
mymath.a
|
||||
|
||||
The file whose suffix is `.a` is the binary file of our packages, and now how can we use it?
|
||||
|
||||
Obviously, we need to create a new application to use it.
|
||||
|
||||
Create a new application package called `mathapp`.
|
||||
|
||||
cd $GOPATH/src
|
||||
mkdir mathapp
|
||||
cd mathapp
|
||||
vim main.go
|
||||
|
||||
code
|
||||
|
||||
//$GOPATH/src/mathapp/main.go source code.
|
||||
package main
|
||||
|
||||
import (
|
||||
"mymath"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
|
||||
}
|
||||
|
||||
To compile this application, you need to switch to the application directory which is `$GOPATH/src/mathapp` in this case, then execute command `go install`. Now you should see an executable file called `mathapp` was generated in the directory `$GOPATH/bin/`. To run this program, use command `./mathapp`, you should see following content in your terminal.
|
||||
|
||||
Hello world. Sqrt(2) = 1.414213562373095
|
||||
|
||||
## Install remote packages
|
||||
|
||||
Go has a tool for installing remote packages, which is the command called `go get`. It supports most of open source communities, including Github, Google Code, BitBucket, and Launchpad.
|
||||
|
||||
go get github.com/astaxie/beedb
|
||||
|
||||
You can use `go get -u …` to update your remote packages, and it will automatically install all the dependent packages as well.
|
||||
|
||||
This tool will use different version control tools for different open source platforms. For example, `git` for Github, `hg` for Google Code. Therefore you have to install these version control tools before you use `go get`.
|
||||
|
||||
After executed above commands, the directory structure should look like following.
|
||||
|
||||
$GOPATH
|
||||
src
|
||||
|-github.com
|
||||
|-astaxie
|
||||
|-beedb
|
||||
pkg
|
||||
|--${GOOS}_${GOARCH}
|
||||
|-github.com
|
||||
|-astaxie
|
||||
|-beedb.a
|
||||
|
||||
Actually, `go get` clones source code to $GOPATH/src of local file system, then executes `go install`.
|
||||
|
||||
Use remote packages is the same way as we use local packages.
|
||||
|
||||
import "github.com/astaxie/beedb"
|
||||
|
||||
## Directory complete structure
|
||||
|
||||
If you follow all steps, your directory structure should look like the following now.
|
||||
|
||||
bin/
|
||||
mathapp
|
||||
pkg/
|
||||
${GOOS}_${GOARCH}, such as darwin_amd64, linux_amd64
|
||||
mymath.a
|
||||
github.com/
|
||||
astaxie/
|
||||
beedb.a
|
||||
src/
|
||||
mathapp
|
||||
main.go
|
||||
mymath/
|
||||
sqrt.go
|
||||
github.com/
|
||||
astaxie/
|
||||
beedb/
|
||||
beedb.go
|
||||
util.go
|
||||
|
||||
Now you are able to see the directory structure clearly, `bin` contains executable files, `pkg` contains compiled files, `src` contains package source files.
|
||||
|
||||
(The format of environment variables in Windows is `%GOPATH%`, this book mainly uses Unix-style, so Windows users need to replace by yourself.)
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Installation](01.1.md)
|
||||
- Next section: [Go commands](01.3.md)
|
||||
104
en/eBook/01.3.md
Normal file
104
en/eBook/01.3.md
Normal file
@@ -0,0 +1,104 @@
|
||||
#1.3 Go commands
|
||||
|
||||
## Go commands
|
||||
|
||||
Go language comes with a complete set of command operation tool, you can execute the command line `go` to see them:
|
||||
|
||||

|
||||
|
||||
Figure 1.3 Go command displays detailed information
|
||||
|
||||
These are all useful for us, let's see how to use some of them.
|
||||
|
||||
## go build
|
||||
|
||||
This command is for compiling tests, it will compile dependence packages if it's necessary.
|
||||
|
||||
- If the package is not the `main` package such as `mymath` in section 1.2, nothing will be generated after you executed `go build`. If you need package file `.a` in `$GOPATH/pkg`, use `go install` instead.
|
||||
- If the package is the `main` package, it will generate an executable file in the same folder. If you want the file to be generated in `$GOPATH/bin`, use `go install` or `go build -o ${PATH_HERE}/a.exe.`
|
||||
- If there are many files in the folder, but you just want to compile one of them, you should append file name after `go build`. For example, `go build a.go`. `go build` will compile all the files in the folder.
|
||||
- You can also assign the name of file that will be generated. For instance, we have `mathapp` in section 1.2, use `go build -o astaxie.exe` will generate `astaxie.exe` instead of `mathapp.exe`. The default name is your folder name(non-main package) or the first source file name(main package).
|
||||
|
||||
(According to [The Go Programming Language Specification](https://golang.org/ref/spec), package name should be the name after the word `package` in the first line of your source files, it doesn't have to be the same as folder's, and the executable file name will be your folder name as default.])
|
||||
|
||||
- `go build` ignores files whose name starts with `_` or `.`.
|
||||
- If you want to have different source files for every operating system, you can name files with system name as suffix. Suppose there are some source files for loading arrays, they could be named as follows.
|
||||
|
||||
array_linux.go | array_darwin.go | array_windows.go | array_freebsd.go
|
||||
|
||||
`go build` chooses the one that associated with your operating system. For example, it only compiles array_linux.go in Linux systems, and ignores all the others.
|
||||
|
||||
## go clean
|
||||
|
||||
This command is for clean files that are generated by compilers, including following files.
|
||||
|
||||
_obj/ // old directory of object, left by Makefiles
|
||||
_test/ // old directory of test, left by Makefiles
|
||||
_testmain.go // old directory of gotest, left by Makefiles
|
||||
test.out // old directory of test, left by Makefiles
|
||||
build.out // old directory of test, left by Makefiles
|
||||
*.[568ao] // object files, left by Makefiles
|
||||
|
||||
DIR(.exe) // generated by go build
|
||||
DIR.test(.exe) // generated by go test -c
|
||||
MAINFILE(.exe) // generated by go build MAINFILE.go
|
||||
|
||||
I usually use this command to clean my files before I upload my project to the Github, these are useful for local tests, but useless for version control.
|
||||
|
||||
## go fmt
|
||||
|
||||
The people who are working with C/C++ should know that people are always arguing about code style between K&R-style and ANSI-style, which one is better. However in Go, there is only one code style which is forced to use. For example, you must put left brace in the end of the line, and can't put it in a single line, otherwise you will get compile errors! Fortunately, you don't have to remember these rules, `go fmt` does this job for you, just execute command `go fmt <File name>.go` in terminal. I don't use this command very much because IDEs usually execute this command automatically when you save source files, I will talk about IDEs more in next section.
|
||||
|
||||
We usually use `gofmt -w` instead of `go fmt`, the latter will not rewrite your source files after formatted code. `gofmt -w src` formats the whole project.
|
||||
|
||||
## go get
|
||||
|
||||
This command is for getting remote packages, it supports BitBucket, Github, Google Code, Launchpad so far. There are actually two things happening after we executed this command. The first thing is to download source code, then executes `go install`. Before you use this command, make sure you have installed related tools.
|
||||
|
||||
BitBucket (Mercurial Git)
|
||||
Github (git)
|
||||
Google Code (Git, Mercurial, Subversion)
|
||||
Launchpad (Bazaar)
|
||||
|
||||
In order to use this command, you have to install these tools correctly. Don't forget to set `PATH`. By the way, it also supports customized domain names, use `go help remote` for more details.
|
||||
|
||||
## go install
|
||||
|
||||
This command compiles all packages and generate files, then move them to `$GOPATH/pkg` or `$GOPATH/bin`.
|
||||
|
||||
## go test
|
||||
|
||||
This command loads all files whose name include `*_test.go` and generate test files, then prints information looks like follows.
|
||||
|
||||
ok archive/tar 0.011s
|
||||
FAIL archive/zip 0.022s
|
||||
ok compress/gzip 0.033s
|
||||
...
|
||||
|
||||
It tests all your test files as default, use command `go help testflag` for more details.
|
||||
|
||||
## go doc
|
||||
|
||||
Many people said that we don't need any third-party documentation for programming in Go(actually I've made a [CHM](https://github.com/astaxie/godoc) already), Go has a powerful tool to manage documentation by itself.
|
||||
|
||||
So how to look up packages' information in documentation? If you want to get more details about package `builtin`, use command `go doc builtin`, and use command `go doc net/http` for package `http`. If you want to see more details about specific functions, use command `godoc fmt Printf`, and `godoc -src fmt Printf` to view source code.
|
||||
|
||||
Execute command `godoc -http=:8080`, then open `127.0.0.1:8080` in your browsers, you should see a localized golang.org. It can not only show the standard packages' information, but also packages in your `$GOPATH/pkg`. It's great for people who are suffering from the Great Firewall of China.
|
||||
|
||||
## Other commands
|
||||
|
||||
Go provides more commands then I just talked about.
|
||||
|
||||
go fix // upgrade code from old version before go1 to new version after go1
|
||||
go version // get information about Go version
|
||||
go env // view environment variables about Go
|
||||
go list // list all installed packages
|
||||
go run // compile temporary files and run the application
|
||||
|
||||
There are also more details about commands that I talked about, you can use `go help <command>` to get more information.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [$GOPATH and workspace](01.2.md)
|
||||
- Next section: [Go development tools](01.4.md)
|
||||
398
en/eBook/01.4.md
Normal file
398
en/eBook/01.4.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Go development tools
|
||||
|
||||
In this section, I'm going to show you some IDEs that have abilities of intelligent completion and auto-format. There are all cross-platform, so the steps I show you should not be very different if you are not using same operating system.
|
||||
|
||||
## LiteIDE
|
||||
|
||||
LiteIDE is an open source, lightweight IDE for developing Go project only, developed by visualfc.
|
||||
|
||||

|
||||
|
||||
Figure 1.4 Main panel of LiteIDE
|
||||
|
||||
LiteIDE features.
|
||||
|
||||
- Cross-platform
|
||||
- Windows
|
||||
- Linux
|
||||
- Mac OS
|
||||
- Cross-compile
|
||||
- Manage multiple compile environment
|
||||
- Supports cross-compile of Go
|
||||
- Project management standard
|
||||
- Documentation view based on $GOPATH
|
||||
- Compile system based on $GOPATH
|
||||
- API documentation index based on $GOPATH
|
||||
- Go source code editor
|
||||
- Code outlining
|
||||
- Full support of gocode
|
||||
- Go documentation view and API index
|
||||
- View code expression by `F1`
|
||||
- Function declaration jump by `F2`
|
||||
- Gdb support
|
||||
- Auto-format with `gofmt`
|
||||
- Others
|
||||
- Multi-language
|
||||
- Plugin system
|
||||
- Text editor themes
|
||||
- Syntax support based on Kate
|
||||
- intelligent completion based on full-text
|
||||
- Customized shortcuts
|
||||
- Markdown support
|
||||
- Real-time preview
|
||||
- Customized CSS
|
||||
- Export HTML and PDF
|
||||
- Convert and merge to HTML and PDF
|
||||
|
||||
### LiteIDE installation
|
||||
|
||||
- Install LiteIDE
|
||||
- [Download page](http://code.google.com/p/golangide)
|
||||
- [Source code](https://github.com/visualfc/liteide)
|
||||
|
||||
You need to install Go first, then download the version of your operating system. Decompress the package to direct use.
|
||||
- Install gocode
|
||||
|
||||
You have to install gocode in order to use intelligent completion
|
||||
|
||||
go get -u github.com/nsf/gocode
|
||||
|
||||
- Compile environment
|
||||
|
||||
Switch configuration in LiteIDE that fits your operating system.
|
||||
In Windows and 64-bit version of Go, you should choose win64 in environment configuration in tool bar. Then you choose `opinion`, find `LiteEnv` in the left list, open file `win64.env` in the right list.
|
||||
|
||||
GOROOT=c:\go
|
||||
GOBIN=
|
||||
GOARCH=amd64
|
||||
GOOS=windows
|
||||
CGO_ENABLED=1
|
||||
|
||||
PATH=%GOBIN%;%GOROOT%\bin;%PATH%
|
||||
。。。
|
||||
|
||||
Replace `GOROOT=c:\go` to your Go installation path, save it. If you have MinGW64, add `c:\MinGW64\bin` to your path environment variable for `cgo` support.
|
||||
|
||||
In Linux and 64-bit version of Go, you should choose linux64 in environment configuration in tool bar. Then you choose `opinion`, find `LiteEnv` in the left list, open file `linux64.env` in the right list.
|
||||
|
||||
GOROOT=$HOME/go
|
||||
GOBIN=
|
||||
GOARCH=amd64
|
||||
GOOS=linux
|
||||
CGO_ENABLED=1
|
||||
|
||||
PATH=$GOBIN:$GOROOT/bin:$PATH
|
||||
。。。
|
||||
|
||||
Replace `GOROOT=$HOME/go` to your Go installation path, save it.
|
||||
- $GOPATH
|
||||
$GOPATH is the path that contains project list, open command tool (or press `Ctrl+` in LiteIDE) then type `go help gopath` for more details.
|
||||
It's very easy to view and change $GOPATH in the LiteIDE. Follow `View - Setup GOPATH` to view and change these values.
|
||||
|
||||
## Sublime Text
|
||||
|
||||
Here I'm going to introduce you the Sublime Text 2 (Sublime for short) + GoSublime + gocode + MarGo. Let me explain why.
|
||||
|
||||
- Intelligent completion
|
||||
|
||||

|
||||
|
||||
Figure 1.5 Sublime intelligent completion
|
||||
- Auto-format source files
|
||||
- Project management
|
||||
|
||||

|
||||
|
||||
Figure 1.6 Sublime project management
|
||||
|
||||
- Syntax highlight
|
||||
- Free trail forever, no functions limit. It will pop-up unregistered prompt sometimes, but you can just ignore it.
|
||||
|
||||
First, download the version of [Sublime](http://www.sublimetext.com/) that fits your operating system.
|
||||
|
||||
1. Press `Ctrl+` ` open command tool, input following commands.
|
||||
|
||||
import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation'
|
||||
|
||||
Restart when installation finished. Then you can find `Package Control` item in the Preferences menu.
|
||||
|
||||

|
||||
|
||||
Figure 1.7 Sublime Package Control
|
||||
2. To install GoSublime, SidebarEnhancements and Go Build, press `Ctrl+Shift+p` to open Package Control, then type `pcip` (short for "Package Control: Install Package").
|
||||
|
||||

|
||||
|
||||
Figure 1.8 Sublime Install Packages
|
||||
|
||||
Now you type GoSublime, press OK to install, same steps for installing SidebarEnhancements and Go Build. Restart when it finished installation.
|
||||
3. To verify if installation is successful, open Sublime, then open file `main.go` to see if it has syntax highlight, type `import` to see if it has prompts, after typed `import "fmt"`, type `fmt.` to see if it has intelligent completion for functions.
|
||||
|
||||
If everything is fine, you're all set.
|
||||
|
||||
If not, check your $PATH again. Open terminal, type `gocode`, if it cannot run, your $PATH must not configure correctly.
|
||||
|
||||
## Vim
|
||||
|
||||
Vim a popular text editor for programmers, which is developing from vi. It has functions for intelligent completion, compile and jump to errors.
|
||||
|
||||

|
||||
|
||||
Figure 1.8 Vim intelligent completion for Go
|
||||
|
||||
1. Syntax highlight for Go
|
||||
|
||||
cp -r $GOROOT/misc/vim/* ~/.vim/
|
||||
|
||||
2. Set syntax highlight on
|
||||
|
||||
filetype plugin indent on
|
||||
syntax on
|
||||
|
||||
3. Install [gocode](https://github.com/nsf/gocode/)
|
||||
|
||||
go get -u github.com/nsf/gocode
|
||||
|
||||
gocode will be installed in `$GOBIN` as default
|
||||
4. Configure [gocode](https://github.com/nsf/gocode/)
|
||||
|
||||
~ cd $GOPATH/src/github.com/nsf/gocode/vim
|
||||
~ ./update.bash
|
||||
~ gocode set propose-builtins true
|
||||
propose-builtins true
|
||||
~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
~ gocode set
|
||||
propose-builtins true
|
||||
lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
|
||||
Explanation of gocode set.
|
||||
|
||||
propose-builtins: whether open intelligent completion or not, false as default.
|
||||
|
||||
lib-path: gocode only search packages in `$GOPATH/pkg/$GOOS_$GOARCH` and `$GOROOT/pkg/$GOOS_$GOARCH`, this setting can add additional path.
|
||||
5. Congratulations! Try `:e main.go` to experience the world of Go!
|
||||
|
||||
## Emacs
|
||||
|
||||
Emacs is so-called Weapon of God. She is not only an editor, also a powerful IDE.
|
||||
|
||||

|
||||
|
||||
Figure 1.10 Emacs main panel of Go editor
|
||||
|
||||
1. Syntax highlight
|
||||
|
||||
cp $GOROOT/misc/emacs/* ~/.emacs.d/
|
||||
|
||||
2. Install [gocode](https://github.com/nsf/gocode/)
|
||||
|
||||
go get -u github.com/nsf/gocode
|
||||
|
||||
gocode will be installed in `$GOBIN` as default
|
||||
3. Configure [gocode](https://github.com/nsf/gocode/)
|
||||
|
||||
~ cd $GOPATH/src/github.com/nsf/gocode/vim
|
||||
~ ./update.bash
|
||||
~ gocode set propose-builtins true
|
||||
propose-builtins true
|
||||
~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
~ gocode set
|
||||
propose-builtins true
|
||||
lib-path "/home/border/gocode/pkg/linux_amd64"
|
||||
|
||||
4. Install [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete)
|
||||
Download and uncompress
|
||||
|
||||
~ make install DIR=$HOME/.emacs.d/auto-complete
|
||||
|
||||
Configure ~/.emacs file
|
||||
|
||||
;;auto-complete
|
||||
(require 'auto-complete-config)
|
||||
(add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict")
|
||||
(ac-config-default)
|
||||
(local-set-key (kbd "M-/") 'semantic-complete-analyze-inline)
|
||||
(local-set-key "." 'semantic-complete-self-insert)
|
||||
(local-set-key ">" 'semantic-complete-self-insert)
|
||||
|
||||
Follow this [link](http://www.emacswiki.org/emacs/AutoComplete) for more details.
|
||||
5. Configure .emacs
|
||||
|
||||
;; golang mode
|
||||
(require 'go-mode-load)
|
||||
(require 'go-autocomplete)
|
||||
;; speedbar
|
||||
;; (speedbar 1)
|
||||
(speedbar-add-supported-extension ".go")
|
||||
(add-hook
|
||||
'go-mode-hook
|
||||
'(lambda ()
|
||||
;; gocode
|
||||
(auto-complete-mode 1)
|
||||
(setq ac-sources '(ac-source-go))
|
||||
;; Imenu & Speedbar
|
||||
(setq imenu-generic-expression
|
||||
'(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
|
||||
("func" "^func *\\(.*\\) {" 1)))
|
||||
(imenu-add-to-menubar "Index")
|
||||
;; Outline mode
|
||||
(make-local-variable 'outline-regexp)
|
||||
(setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....")
|
||||
(outline-minor-mode 1)
|
||||
(local-set-key "\M-a" 'outline-previous-visible-heading)
|
||||
(local-set-key "\M-e" 'outline-next-visible-heading)
|
||||
;; Menu bar
|
||||
(require 'easymenu)
|
||||
(defconst go-hooked-menu
|
||||
'("Go tools"
|
||||
["Go run buffer" go t]
|
||||
["Go reformat buffer" go-fmt-buffer t]
|
||||
["Go check buffer" go-fix-buffer t]))
|
||||
(easy-menu-define
|
||||
go-added-menu
|
||||
(current-local-map)
|
||||
"Go tools"
|
||||
go-hooked-menu)
|
||||
|
||||
;; Other
|
||||
(setq show-trailing-whitespace t)
|
||||
))
|
||||
;; helper function
|
||||
(defun go ()
|
||||
"run current buffer"
|
||||
(interactive)
|
||||
(compile (concat "go run " (buffer-file-name))))
|
||||
|
||||
;; helper function
|
||||
(defun go-fmt-buffer ()
|
||||
"run gofmt on current buffer"
|
||||
(interactive)
|
||||
(if buffer-read-only
|
||||
(progn
|
||||
(ding)
|
||||
(message "Buffer is read only"))
|
||||
(let ((p (line-number-at-pos))
|
||||
(filename (buffer-file-name))
|
||||
(old-max-mini-window-height max-mini-window-height))
|
||||
(show-all)
|
||||
(if (get-buffer "*Go Reformat Errors*")
|
||||
(progn
|
||||
(delete-windows-on "*Go Reformat Errors*")
|
||||
(kill-buffer "*Go Reformat Errors*")))
|
||||
(setq max-mini-window-height 1)
|
||||
(if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t))
|
||||
(progn
|
||||
(erase-buffer)
|
||||
(insert-buffer-substring "*Go Reformat Output*")
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- p)))
|
||||
(with-current-buffer "*Go Reformat Errors*"
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "<standard input>" nil t)
|
||||
(replace-match filename))
|
||||
(goto-char (point-min))
|
||||
(compilation-mode))))
|
||||
(setq max-mini-window-height old-max-mini-window-height)
|
||||
(delete-windows-on "*Go Reformat Output*")
|
||||
(kill-buffer "*Go Reformat Output*"))))
|
||||
;; helper function
|
||||
(defun go-fix-buffer ()
|
||||
"run gofix on current buffer"
|
||||
(interactive)
|
||||
(show-all)
|
||||
(shell-command-on-region (point-min) (point-max) "go tool fix -diff"))
|
||||
6. Congratulations! speedbar is closed as default, cut comment symbols in line `;;(speedbar 1)` to have this feature, or you can have it through `M-x speedbar`.
|
||||
|
||||
## Eclipse
|
||||
|
||||
Eclipse is also a great development tool, I'll show you how to use it to write Go programs.
|
||||
|
||||

|
||||
|
||||
Figure 1.1 Eclipse main panel of Go editor
|
||||
|
||||
1. Download and install [Eclipse](http://www.eclipse.org/)
|
||||
2. Download [goclipse](https://code.google.com/p/goclipse/)
|
||||
[http://code.google.com/p/goclipse/wiki/InstallationInstructions](http://code.google.com/p/goclipse/wiki/InstallationInstructions)
|
||||
3. Download gocode
|
||||
|
||||
gocode in Github.
|
||||
|
||||
https://github.com/nsf/gocode
|
||||
|
||||
You need to install git in Windows, usually we use [msysgit](https://code.google.com/p/msysgit/)
|
||||
|
||||
Install gocode in the command tool
|
||||
|
||||
go get -u github.com/nsf/gocode
|
||||
|
||||
You can install from source code if you like.
|
||||
4. Download and install [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)
|
||||
5. Configure plugins.
|
||||
|
||||
Windows->Preferences->Go
|
||||
|
||||
(1).Configure Go compiler
|
||||
|
||||

|
||||
|
||||
Figure 1.12 Go Setting in Eclipse
|
||||
|
||||
(2).Configure gocode(optional), set gocode path to where the gocode.exe is.
|
||||
|
||||

|
||||
|
||||
Figure 1.13 gocode Setting
|
||||
|
||||
(3).Configure gdb(optional), set gdb path to where the gdb.exe is.
|
||||
|
||||

|
||||
|
||||
Figure 1.14 gdb Setting
|
||||
6. Check installation
|
||||
|
||||
Create a new Go project and hello.go file as following.
|
||||
|
||||

|
||||
|
||||
Figure 1.15 Create new project and file
|
||||
|
||||
Test installation as follows.(you need to type command in console in Eclipse)
|
||||
|
||||

|
||||
|
||||
Figure 1.16 Test Go program in Eclipse
|
||||
|
||||
## IntelliJ IDEA
|
||||
|
||||
People who are working with Java should be familiar with this IDE, it supports Go syntax highlight, intelligent completion and reconstructed by a plugin.
|
||||
|
||||
1. Download IDEA, there is no different from Ultimate and Community Edition
|
||||
|
||||

|
||||
|
||||
2. Install Go plugin. Choose `File - Setting - Plugins`, then click `Browser repo`.
|
||||
|
||||

|
||||
|
||||
3. Search `golang`, double click `download and install`, wait for downloading.
|
||||
|
||||

|
||||
|
||||
Click `Apply`, then restart.
|
||||
4. Now you can create Go project.
|
||||
|
||||

|
||||
|
||||
Input position of go sdk in the next step, basically it's your $GOROOT.
|
||||
|
||||
( ***See a [blog post](http://wuwen.org/tips-about-using-intellij-idea-and-go/) for setup and use IntelliJ IDEA with Go step by step *** )
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Go commands](01.3.md)
|
||||
- Next section: [Summary](01.5.md)
|
||||
9
en/eBook/01.5.md
Normal file
9
en/eBook/01.5.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 1.5 Summary
|
||||
|
||||
In this chapter, we talked about how to install Go through three ways, including from source code, standard package and third-party tools. Then we showed you how to configure Go development environment, mainly about `$GOPATH`. After that, we introduced the steps in compile and deployment of Go programs. Then we talked about Go commands, these commands including compile, install, format, test. Finally, there are many powerful tools to develop Go programs, such as LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Go development tools](01.4.md)
|
||||
- Next chapter: [Go basic knowledge](02.0.md)
|
||||
17
en/eBook/02.0.md
Normal file
17
en/eBook/02.0.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2 Go basic knowledge
|
||||
|
||||
Go is a compiled system programming language, and it belongs to the C-family. However, its compilation speed is much faster than other C-family languages. It has only 25 keywords, even less than 26 English letters! Let's take a look at these keywords before we get started.
|
||||
|
||||
break default func interface select
|
||||
case defer go map struct
|
||||
chan else goto package switch
|
||||
const fallthrough if range type
|
||||
continue for import return var
|
||||
|
||||
In this chapter, I'm going to teach you some basic Go knowledge. You will find how concise the Go programming language is, and the beautiful design of the language. Programming can be very fun in Go. After we complete this chapter, you'll be familiar with the above keywords.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous chapter: [Chapter 1 Summary](01.5.md)
|
||||
- Next section: ["Hello, Go"](02.1.md)
|
||||
53
en/eBook/02.1.md
Normal file
53
en/eBook/02.1.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 2.1 Hello, Go
|
||||
|
||||
Before we start building an application in Go, we need to learn how to write a simple program. It's like you cannot build a building without knowing how to build its foundation. Therefore, we are going to learn the basic syntax to run some simple programs in this section.
|
||||
|
||||
## Program
|
||||
|
||||
According to international practice, before you learn how to programming in some languages, you may want to know how to write a program to print "Hello world".
|
||||
|
||||
Are you ready? Let's Go!
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n")
|
||||
}
|
||||
|
||||
It prints following information.
|
||||
|
||||
Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界
|
||||
|
||||
## Explanation
|
||||
|
||||
One thing that you should know in the first is that Go programs are composed by `package`.
|
||||
|
||||
`package<pkgName>` (In this case is `package main`) tells us this source file belongs to `main` package, and the keyword `main` tells us this package will be compiled to a program instead of package files whose extensions are `.a`.
|
||||
|
||||
Every executable program has one and only one `main` package, and you need an entry function called `main` without any argument and return value in the `main` package.
|
||||
|
||||
In order to print `Hello, world…`, we called a function called `Printf`. This function is coming from `fmt` package, so we import this package in the third line of source code, which is `import "fmt"`
|
||||
|
||||
The way to think about packages in Go is similar to Python, and there are some advantages: Modularity (break up your program into many modules) and reusability (every module can be reused in many programs). We just talked about concepts regarding packages, and we will make our own packages later.
|
||||
|
||||
On the fifth line, we use the keyword `func` to define the `main` function. The body of the function is inside of `{}`, just like C, C++ and Java.
|
||||
|
||||
As you can see, there are no arguments. We will learn how to write functions with arguments in just a second, and you can also have functions that have no return value or have several return values.
|
||||
|
||||
On the sixth line, we called the function `Printf` which is from the package `fmt`. This was called by the syntax `<pkgName>.<funcName>`, which is very like Python-style.
|
||||
|
||||
As we mentioned in chapter 1, the package's name and the name of the folder that contains that package can be different. Here the `<pkgName>` comes from the name in `package <pkgName>`, not the folder's name.
|
||||
|
||||
You may notice that the example above contains many non-ASCII characters. The purpose of showing this is to tell you that Go supports UTF-8 by default. You can use any UTF-8 character in your programs.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Go uses `package` (like modules in Python) to organize programs. The function `main.main()` (this function must be in the `main` package) is the entry point of any program. Go supports UTF-8 characters because one of the creators of Go is a creator of UTF-8, so Go supports multi-language from the time it was born.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Go basic knowledge](02.0.md)
|
||||
- Next section: [Go foundation](02.2.md)
|
||||
456
en/eBook/02.2.md
Normal file
456
en/eBook/02.2.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 2.2 Go foundation
|
||||
|
||||
In this section, we are going to teach you how to define constants, variables with elementary types and some skills in Go programming.
|
||||
|
||||
## Define variables
|
||||
|
||||
There are many forms of syntax that can define variables in Go.
|
||||
|
||||
Use keyword `var` is the basic form to define variables, notice that Go puts variable type `after` variable name.
|
||||
|
||||
// define a variable with name “variableName” and type "type"
|
||||
var variableName type
|
||||
|
||||
Define multiple variables.
|
||||
|
||||
// define three variables which types are "type"
|
||||
var vname1, vname2, vname3 type
|
||||
|
||||
Define a variable with initial value.
|
||||
|
||||
// define a variable with name “variableName”, type "type" and value "value"
|
||||
var variableName type = value
|
||||
|
||||
Define multiple variables with initial values.
|
||||
|
||||
/*
|
||||
Define three variables with type "type", and initialize their values.
|
||||
vname1 is v1, vname2 is v2, vname3 is v3
|
||||
*/
|
||||
var vname1, vname2, vname3 type = v1, v2, v3
|
||||
|
||||
Do you think it's too tedious to define variables use the way above? Don't worry because Go team found this problem as well. Therefore if you want to define variables with initial values, we can just omit variable type, so the code will look like this:
|
||||
|
||||
/*
|
||||
Define three variables with type "type", and initialize their values.
|
||||
vname1 is v1,vname2 is v2,vname3 is v3
|
||||
*/
|
||||
var vname1, vname2, vname3 = v1, v2, v3
|
||||
|
||||
Well, I know this is still not simple enough for you, so do I. Let's see how we fix it.
|
||||
|
||||
/*
|
||||
Define three variables with type "type", and initialize their values.
|
||||
vname1 is v1,vname2 is v2,vname3 is v3
|
||||
*/
|
||||
vname1, vname2, vname3 := v1, v2, v3
|
||||
|
||||
Now it looks much better. Use `:=` to replace `var` and `type`, this is called brief statement. But wait, it has one limitation that this form can only be used inside of functions. You will get compile errors if you try to use it outside of function bodies. Therefore, we usually use `var` to define global variables, and we can use this brief statement in `var()`.
|
||||
|
||||
`_` (blank) is a special name of variable, any value that is given to it will be ignored. For example, we give `35` to `b`, and discard `34`.( ***This example just show you how it works. It looks useless here because we often use this symbol when we get function return values.*** )
|
||||
|
||||
_, b := 34, 35
|
||||
|
||||
If you don't use any variable that you defined in the program, compiler will give you compile errors. Try to compile following code, see what happens.
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
var i int
|
||||
}
|
||||
|
||||
## Constants
|
||||
|
||||
So-called constants are the values that are determined in the compile time, and you cannot change them during runtime. In Go, you can use number, boolean or string as type of constants.
|
||||
|
||||
Define constants as follows.
|
||||
|
||||
const constantName = value
|
||||
// you can assign type of constants if it's necessary
|
||||
const Pi float32 = 3.1415926
|
||||
|
||||
More examples.
|
||||
|
||||
const Pi = 3.1415926
|
||||
const i = 10000
|
||||
const MaxThread = 10
|
||||
const prefix = "astaxie_"
|
||||
|
||||
## Elementary types
|
||||
|
||||
### Boolean
|
||||
|
||||
In Go, we use `bool` to define a variable as boolean type, the value can only be `true` or `false`, and `false` will be the default value. ( ***You cannot convert variables' type between number and boolean!*** )
|
||||
|
||||
// sample code
|
||||
var isActive bool // global variable
|
||||
var enabled, disabled = true, false // omit type of variables
|
||||
func test() {
|
||||
var available bool // local variable
|
||||
valid := false // brief statement of variable
|
||||
available = true // assign value to variable
|
||||
}
|
||||
|
||||
### Numerical types
|
||||
|
||||
Integer types including signed and unsigned integer types. Go has `int` and `uint` at the same time, they have same length, but specific length depends on your operating system. They use 32-bit in 32-bit operating systems, and 64-bit in 64-bit operating systems. Go also has types that have specific length including `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Note that `rune` is alias of `int32` and `byte` is alias of `uint8`.
|
||||
|
||||
One important thing you should know that you cannot assign values between these types, this operation will cause compile errors.
|
||||
|
||||
var a int8
|
||||
|
||||
var b int32
|
||||
|
||||
c := a + b
|
||||
|
||||
Although int has longer length than uint8, and has same length as int32, but you cannot assign values between them. ( ***c will be asserted as type `int` here*** )
|
||||
|
||||
Float types have `float32` and `float64`, and no type called `float`, latter one is default type if you use brief statement.
|
||||
|
||||
That's all? No! Go has complex number as well. `complex128` (with a 64-bit real and 64-bit imaginary part)is default type, if you need smaller type, there is one called `complex64` (with a 32-bit real and 32-bit imaginary part). Its form is `RE+IMi`, where `RE` is real part and `IM` is imaginary part, the last `i` is imaginary number. There is a example of complex number.
|
||||
|
||||
var c complex64 = 5+5i
|
||||
//output: (5+5i)
|
||||
fmt.Printf("Value is: %v", c)
|
||||
|
||||
### String
|
||||
|
||||
We just talked about that Go uses UTF-8 character set. Strings are represented by double quotes `""` or backtracks ``` `.
|
||||
|
||||
// sample code
|
||||
var frenchHello string // basic form to define string
|
||||
var emptyString string = "" // define a string with empty string
|
||||
func test() {
|
||||
no, yes, maybe := "no", "yes", "maybe" // brief statement
|
||||
japaneseHello := "Ohaiou"
|
||||
frenchHello = "Bonjour" // basic form of assign values
|
||||
}
|
||||
|
||||
It's impossible to change string values by index, you will get errors when you compile following code.
|
||||
|
||||
var s string = "hello"
|
||||
s[0] = 'c'
|
||||
|
||||
What if I really want to change just one character in a string? Try following code.
|
||||
|
||||
s := "hello"
|
||||
c := []byte(s) // convert string to []byte type
|
||||
c[0] = 'c'
|
||||
s2 := string(c) // convert back to string type
|
||||
fmt.Printf("%s\n", s2)
|
||||
|
||||
You can use operator `+` to combine two strings.
|
||||
|
||||
s := "hello,"
|
||||
m := " world"
|
||||
a := s + m
|
||||
fmt.Printf("%s\n", a)
|
||||
|
||||
and also.
|
||||
|
||||
s := "hello"
|
||||
s = "c" + s[1:] // you cannot change string values by index, but you can get values instead.
|
||||
fmt.Printf("%s\n", s)
|
||||
|
||||
What if I want to have a multiple-line string?
|
||||
|
||||
m := `hello
|
||||
world`
|
||||
|
||||
`` ` will not escape any characters in a string.
|
||||
|
||||
### Error types
|
||||
|
||||
Go has one `error` type for purpose of dealing with error messages. There is also a package called `errors` to handle errors.
|
||||
|
||||
err := errors.New("emit macho dwarf: elf header corrupted")
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
|
||||
### Underlying data structure
|
||||
|
||||
The following picture comes from a article about [Go data structure](http://research.swtch.com/godata) in [Russ Cox Blog](http://research.swtch.com/). As you can see, Go gives blocks in memory to store data.
|
||||
|
||||

|
||||
|
||||
Figure 2.1 Go underlying data structure
|
||||
|
||||
## Some skills
|
||||
|
||||
### Define by group
|
||||
|
||||
If you want to define multiple constants, variables or import packages, you can use group form.
|
||||
|
||||
Basic form.
|
||||
|
||||
import "fmt"
|
||||
import "os"
|
||||
|
||||
const i = 100
|
||||
const pi = 3.1415
|
||||
const prefix = "Go_"
|
||||
|
||||
var i int
|
||||
var pi float32
|
||||
var prefix string
|
||||
|
||||
Group form.
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const(
|
||||
i = 100
|
||||
pi = 3.1415
|
||||
prefix = "Go_"
|
||||
)
|
||||
|
||||
var(
|
||||
i int
|
||||
pi float32
|
||||
prefix string
|
||||
)
|
||||
|
||||
Unless you assign the value of constant is `iota`, the first value of constant in the group `const()` will be `0`. If following constants don't assign values explicitly, their values will be the same as the last one. If the value of last constant is `iota`, the values of following constants which are not assigned are `iota` also.
|
||||
|
||||
### iota enumerate
|
||||
|
||||
Go has one keyword `iota`, this keyword is to make `enum`, it begins with `0`, increased by `1`.
|
||||
|
||||
const(
|
||||
x = iota // x == 0
|
||||
y = iota // y == 1
|
||||
z = iota // z == 2
|
||||
w // If there is no expression after constants name, it uses the last expression, so here is saying w = iota implicitly. Therefore w == 3, and y and x both can omit "= iota" as well.
|
||||
)
|
||||
|
||||
const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0.
|
||||
|
||||
const (
|
||||
e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line.
|
||||
)
|
||||
|
||||
### Some rules
|
||||
|
||||
The reason that Go is concise because it has some default behaviors.
|
||||
|
||||
- Any variable starts with capital letter means it will be exported, private otherwise.
|
||||
- Same rule for functions and constants, no `public` or `private` keyword exists in Go.
|
||||
|
||||
## array, slice, map
|
||||
|
||||
### array
|
||||
|
||||
`array` is array obviously, we define them as follows.
|
||||
|
||||
var arr [n]type
|
||||
|
||||
in `[n]type`, `n` is the length of array, `type` is the type of its elements. Like other languages, we use `[]` to get or set element values in array.
|
||||
|
||||
var arr [10]int // an array of type int
|
||||
arr[0] = 42 // array is 0-based
|
||||
arr[1] = 13 // assign value to element
|
||||
fmt.Printf("The first element is %d\n", arr[0]) // get element value, it returns 42
|
||||
fmt.Printf("The last element is %d\n", arr[9]) //it returns default value of 10th element in this array, which is 0 in this case.
|
||||
|
||||
Because length is a part of array type, `[3]int` and `[4]int` are different types, so we cannot change length of arrays. When you use arrays as arguments, functions get their copies instead of references! If you want to use reference, you may want to use `slice` which we will talk about latter.
|
||||
|
||||
It's possible to use `:=` when you define arrays.
|
||||
|
||||
a := [3]int{1, 2, 3} // define a int array with 3 elements
|
||||
|
||||
b := [10]int{1, 2, 3} // define a int array with 10 elements, and first three are assigned, rest of them use default value 0.
|
||||
|
||||
c := [...]int{4, 5, 6} // use `…` replace with number of length, Go will calculate it for you.
|
||||
|
||||
You may want to use arrays as arrays' elements, let's see how to do it.
|
||||
|
||||
// define a two-dimensional array with 2 elements, and each element has 4 elements.
|
||||
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
|
||||
|
||||
// You can write about declaration in a shorter way.
|
||||
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
|
||||
|
||||
Array underlying data structure.
|
||||
|
||||

|
||||
|
||||
Figure 2.2 Multidimensional array mapping relationship
|
||||
|
||||
### slice
|
||||
|
||||
In many situations, array is not a good choice. For example, we don't know how long the array will be when we define it, so we need "dynamic array". This is called `slice` in Go.
|
||||
|
||||
`slice` is not really `dynamic array`, it's a reference type. `slice` points to an underlying `array`, its declaration is similar to `array`, but doesn't need length.
|
||||
|
||||
// just like to define array, but no length this time
|
||||
var fslice []int
|
||||
|
||||
Then we define a `slice`, and initialize its data.
|
||||
|
||||
slice := []byte {'a', 'b', 'c', 'd'}
|
||||
|
||||
`slice` can redefine from exists slices or arrays. `slice` use `array[i:j]` to slice, where `i` is start index and `j` is end index, but notice that `array[j]` will not be sliced, now the length of slice is `j-i`.
|
||||
|
||||
// define a slice with 10 elements which types are byte
|
||||
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
|
||||
// define two slices with type []byte
|
||||
var a, b []byte
|
||||
|
||||
// a points to elements from 3rd to 5th in array ar.
|
||||
a = ar[2:5]
|
||||
// now a has elements ar[2],ar[3] and ar[4]
|
||||
|
||||
// b is another slice of array ar
|
||||
b = ar[3:5]
|
||||
// now b has elements ar[3] and ar[4]
|
||||
|
||||
Notice that differences between `slice` and `array` when you define them. We use `[…]` let Go calculates length but use `[]` to define slice only.
|
||||
|
||||
Their underlying data structure.
|
||||
|
||||

|
||||
|
||||
Figure 2.3 Correspondence between slice and array
|
||||
|
||||
slice has some convenient operations.
|
||||
|
||||
- `slice` is 0-based, `ar[:n]` equals to `ar[0:n]`
|
||||
- Second index will be the length of `slice` if you omit it, `ar[n:]` equals to `ar[n:len(ar)]`.
|
||||
- You can use `ar[:]` to slice whole array, reasons are explained in first two statements.
|
||||
|
||||
More examples about `slice`
|
||||
|
||||
// define an array
|
||||
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
// define two slices
|
||||
var aSlice, bSlice []byte
|
||||
|
||||
// some convenient operations
|
||||
aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c
|
||||
aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j
|
||||
aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements
|
||||
|
||||
// slice from slice
|
||||
aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7
|
||||
bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f
|
||||
bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f
|
||||
bSlice = aSlice[0:5] // slcie could be expanded in range of cap, now bSlice contains d,e,f,g,h
|
||||
bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g
|
||||
|
||||
`slice` is reference type, so one of them changes will affect others. For instance, `aSlice` and `bSlice` above, if you change value of element in `aSlice`, `bSlice` will be changed as well.
|
||||
|
||||
`slice` is like a struct by definition, it contains 3 parts.
|
||||
|
||||
- A pointer that points to where `slice` starts.
|
||||
- length of `slice`.
|
||||
- Capacity, the length from start index to end index of `slice`.
|
||||
|
||||
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||||
Slice_a := Array_a[2:5]
|
||||
|
||||
Underlying data structure of code above as follows.
|
||||
|
||||

|
||||
|
||||
Figure 2.4 Array information of slice
|
||||
|
||||
There are some built-in functions for slice.
|
||||
|
||||
- `len` gets length of `slice`.
|
||||
- `cap` gets maximum length of `slice`
|
||||
- `append` appends one or more elements to `slice`, and returns `slice` .
|
||||
- `copy` copies elements from one slice to the other, and returns number of elements were copied.
|
||||
|
||||
Attention: `append` will change array that `slice` points to, and affect other slices that point the same array. Also, if there is not enough length for the slice (`(cap-len) == 0`), `append` returns new array for this slice, at this point, other slices point to the old array will not be affected.
|
||||
|
||||
### map
|
||||
|
||||
`map` is like dictionary in Python, use form `map[keyType]valueType` to define it.
|
||||
|
||||
Let's see some code, the set and get value in `map` is like `slice`, use `key` as agent, but index in `slice` can only be int type, and `map` can use much more than that, `int`, `string`, whatever you want. Also, they are all able to use `==` and `!=` to compare values.
|
||||
|
||||
// use string as key type, int as value type, and you have to use `make` initialize it.
|
||||
var numbers map[string] int
|
||||
// another way to define map
|
||||
numbers := make(map[string]int)
|
||||
numbers["one"] = 1 // assign value by key
|
||||
numbers["ten"] = 10
|
||||
numbers["three"] = 3
|
||||
|
||||
fmt.Println("The third number is: ", numbers["three"]) // get values
|
||||
// It prints: The third number is: 3
|
||||
|
||||
`map` is like form in our lives, left side are `key`s, another side are values.
|
||||
|
||||
Some notes when you use map.
|
||||
|
||||
- `map` is disorderly, every time you print `map` will get different results. It's impossible to get value by `index`, you have to use `key`.
|
||||
- `map` doesn't have fixed length, it's a reference type as `slice` does.
|
||||
- `len` works for `map` also, it returns how many `key`s that map has.
|
||||
- It's quite easy to change value through `map`, simply use `numbers["one"]=11` to change value of `key` one to `11`.
|
||||
|
||||
You can use form `key:val` to initialize map's values, and `map` has method inside to check if the `key` exists.
|
||||
|
||||
Use `delete` to delete element in `map`.
|
||||
|
||||
// Initialize a map
|
||||
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
|
||||
// map has two return values. For second value, if the key doesn't exist,ok is false,true otherwise.
|
||||
csharpRating, ok := rating["C#"]
|
||||
if ok {
|
||||
fmt.Println("C# is in the map and its rating is ", csharpRating)
|
||||
} else {
|
||||
fmt.Println("We have no rating associated with C# in the map")
|
||||
}
|
||||
|
||||
delete(rating, "C") // delete element with key "c"
|
||||
|
||||
As I said above, `map` is a reference type, if two `map`s point to same underlying data, any change will affect both of them.
|
||||
|
||||
m := make(map[string]string)
|
||||
m["Hello"] = "Bonjour"
|
||||
m1 := m
|
||||
m1["Hello"] = "Salut" // now the value of m["hello"] is Salut
|
||||
|
||||
### make, new
|
||||
|
||||
`make` does memory allocation for built-in models, such as `map`, `slice`, and `channel`), `new` is for types' memory allocation.
|
||||
|
||||
`new(T)` allocates zero-value to type `T`'s memory, returns its memory address, which is the value of type `*T`. By Go's term, it returns a pointer, which points to type `T`'s zero-value.
|
||||
|
||||
`new` returns pointers.
|
||||
|
||||
Built-in function `make(T, args)` has different purposes from `new(T)`, `make` can be used for `slice`, `map`, and `channel`, and returns a type `T` with initial value. The reason of doing this is because these three types' underlying data must be initialized before they point to them. For example, a `slice` contains a pointer points to underlying `array`, length and capacity. Before these data were initialized, `slice` is `nil`, so for `slice`, `map`, `channel`, `make` initializes their underlying data, and assigns some suitable values.
|
||||
|
||||
`make` returns non-zero values.
|
||||
|
||||
The following picture shows how `new` and `make` be different.
|
||||
|
||||

|
||||
|
||||
Figure 2.5 Underlying memory allocation of make and new
|
||||
|
||||
As for zero-value, it doesn't mean empty value. It's the value that variables are not assigned manually, usually is 0, there is list of some zero-values.
|
||||
|
||||
int 0
|
||||
int8 0
|
||||
int32 0
|
||||
int64 0
|
||||
uint 0x0
|
||||
rune 0 // the actual type of rune is int32
|
||||
byte 0x0 // the actual type of byte is uint8
|
||||
float32 0 // length is 4 byte
|
||||
float64 0 //length is 8 byte
|
||||
bool false
|
||||
string ""
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: ["Hello, Go"](02.1.md)
|
||||
- Next section: [Control statements and functions](02.3.md)
|
||||
516
en/eBook/02.3.md
Normal file
516
en/eBook/02.3.md
Normal file
@@ -0,0 +1,516 @@
|
||||
# 2.3 Control statements and functions
|
||||
|
||||
In this section, we are going to talk about control statements and function operation in Go.
|
||||
|
||||
## Control statement
|
||||
|
||||
The greatest inventions in programming language is flow control. Because of them, you are able to use simple control statements represent complex logic. There are three categories, conditional, cycle control and unconditional jump.
|
||||
|
||||
### if
|
||||
|
||||
`if` is the most common keyword in your programs. If it meets the conditions then does something, does something else if not.
|
||||
|
||||
`if` doesn't need parentheses in Go.
|
||||
|
||||
if x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
|
||||
The most useful thing of `if` in Go is that it can have one initialization statement before the conditional statement. The scope of variables which are defined in this initialization statement is only in the block of `if`.
|
||||
|
||||
// initialize x, then check if x greater than
|
||||
if x := computedValue(); x > 10 {
|
||||
fmt.Println("x is greater than 10")
|
||||
} else {
|
||||
fmt.Println("x is less than 10")
|
||||
}
|
||||
|
||||
// the following code will not compile
|
||||
fmt.Println(x)
|
||||
|
||||
Use if-else for multiple conditions.
|
||||
|
||||
if integer == 3 {
|
||||
fmt.Println("The integer is equal to 3")
|
||||
} else if integer < 3 {
|
||||
fmt.Println("The integer is less than 3")
|
||||
} else {
|
||||
fmt.Println("The integer is greater than 3")
|
||||
}
|
||||
|
||||
### goto
|
||||
|
||||
Go has `goto`, be careful when you use it. `goto` has to jump to the `label` that in the body of same code block.
|
||||
|
||||
func myFunc() {
|
||||
i := 0
|
||||
Here: // label ends with ":"
|
||||
fmt.Println(i)
|
||||
i++
|
||||
goto Here // jump to label "Here"
|
||||
}
|
||||
|
||||
Label name is case sensitive.
|
||||
|
||||
### for
|
||||
|
||||
`for` is the most powerful control logic in Go, it can read data in loops and iterative operations, just like `while`.
|
||||
|
||||
for expression1; expression2; expression3 {
|
||||
//...
|
||||
}
|
||||
|
||||
`expression1`, `expression2` and `expression3` are all expressions obviously, where `expression1` and `expression3` are variable defination or return values from functions, and `expression2` is a conditional statement. `expression1` will be executed before every loop, and `expression3` will be after.
|
||||
|
||||
An example is more useful than hundreds of words.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
func main(){
|
||||
sum := 0;
|
||||
for index:=0; index < 10 ; index++ {
|
||||
sum += index
|
||||
}
|
||||
fmt.Println("sum is equal to ", sum)
|
||||
}
|
||||
// Print:sum is equal to 45
|
||||
|
||||
Sometimes we need multiple assignments, but Go doesn't have operator `,`, so we use parallel assignment like `i, j = i + 1, j - 1`.
|
||||
|
||||
We can omit `expression1` and `expression3` if they are not necessary.
|
||||
|
||||
sum := 1
|
||||
for ; sum < 1000; {
|
||||
sum += sum
|
||||
}
|
||||
|
||||
Omit `;` as well. Feel familiar? Yes, it's `while`.
|
||||
|
||||
sum := 1
|
||||
for sum < 1000 {
|
||||
sum += sum
|
||||
}
|
||||
|
||||
There are two important operations in loops which are `break` and `continue`. `break` jumps out the loop, and `continue` skips current loop and starts next one. If you have nested loops, use `break` with labels together.
|
||||
|
||||
for index := 10; index>0; index-- {
|
||||
if index == 5{
|
||||
break // or continue
|
||||
}
|
||||
fmt.Println(index)
|
||||
}
|
||||
// break prints 10、9、8、7、6
|
||||
// continue prints 10、9、8、7、6、4、3、2、1
|
||||
|
||||
`for` could read data from `slice` and `map` when it is used with `range`.
|
||||
|
||||
for k,v:=range map {
|
||||
fmt.Println("map's key:",k)
|
||||
fmt.Println("map's val:",v)
|
||||
}
|
||||
|
||||
Because Go supports multi-value return and gives compile errors when you don't use values that was defined, so you may want to use `_` to discard some return values.
|
||||
|
||||
for _, v := range map{
|
||||
fmt.Println("map's val:", v)
|
||||
}
|
||||
|
||||
### switch
|
||||
|
||||
Sometimes you may think you use too much `if-else` to implement some logic, also it's not looking nice and herd to maintain in the future. Now it's time to use `switch` to solve this problem.
|
||||
|
||||
switch sExpr {
|
||||
case expr1:
|
||||
some instructions
|
||||
case expr2:
|
||||
some other instructions
|
||||
case expr3:
|
||||
some other instructions
|
||||
default:
|
||||
other code
|
||||
}
|
||||
|
||||
The type of `sExpr`, `expr1`, `expr2`, and `expr3` must be the same. `switch` is very flexible, conditions don't have to be constants, it executes from top to down until it matches conditions. If there is no statement after keyword `switch`, then it matches `true`.
|
||||
|
||||
i := 10
|
||||
switch i {
|
||||
case 1:
|
||||
fmt.Println("i is equal to 1")
|
||||
case 2, 3, 4:
|
||||
fmt.Println("i is equal to 2, 3 or 4")
|
||||
case 10:
|
||||
fmt.Println("i is equal to 10")
|
||||
default:
|
||||
fmt.Println("All I know is that i is an integer")
|
||||
}
|
||||
|
||||
In fifth line, we put many values in one `case`, and we don't need `break` in the end of `case` body. It will jump out of switch body once it matched any case. If you want to continue to match more cases, you need to use statement `fallthrough`.
|
||||
|
||||
integer := 6
|
||||
switch integer {
|
||||
case 4:
|
||||
fmt.Println("integer <= 4")
|
||||
fallthrough
|
||||
case 5:
|
||||
fmt.Println("integer <= 5")
|
||||
fallthrough
|
||||
case 6:
|
||||
fmt.Println("integer <= 6")
|
||||
fallthrough
|
||||
case 7:
|
||||
fmt.Println("integer <= 7")
|
||||
fallthrough
|
||||
case 8:
|
||||
fmt.Println("integer <= 8")
|
||||
fallthrough
|
||||
default:
|
||||
fmt.Println("default case")
|
||||
}
|
||||
|
||||
This program prints following information.
|
||||
|
||||
integer <= 6
|
||||
integer <= 7
|
||||
integer <= 8
|
||||
default case
|
||||
|
||||
## Functions
|
||||
|
||||
Use the keyword `func` to define a function.
|
||||
|
||||
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
|
||||
// function body
|
||||
// multi-value return
|
||||
return value1, value2
|
||||
}
|
||||
|
||||
We can get following information from above example.
|
||||
|
||||
- Use keyword `func` to define a function `funcName`.
|
||||
- Functions have zero or one or more than one arguments, argument type after the argument name and broke up by `,`.
|
||||
- Functions can return multiple values.
|
||||
- There are two return values named `output1` and `output2`, you can omit name and use type only.
|
||||
- If there is only one return value and you omitted the name, you don't need brackets for return values anymore.
|
||||
- If the function doesn't have return values, you can omit return part.
|
||||
- If the function has return values, you have to use `return` statement in some places in the body of function.
|
||||
|
||||
Let's see one practical example. (calculate maximum value)
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
// return greater value between a and b
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
z := 5
|
||||
|
||||
max_xy := max(x, y) // call function max(x, y)
|
||||
max_xz := max(x, z) // call function max(x, z)
|
||||
|
||||
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
|
||||
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
|
||||
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // call function here
|
||||
}
|
||||
|
||||
In the above example, there are two arguments in function `max`, their type are both `int`, so the first type can be omitted, like `a, b int` instead of `a int, b int`. Same rules for more arguments. Notice here the `max` only have one return value, so we only write type of return value, this is a short form.
|
||||
|
||||
### Multi-value return
|
||||
|
||||
One thing that Go is better than C is that it supports multi-value return.
|
||||
|
||||
We use above example here.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
// return results of A + B and A * B
|
||||
func SumAndProduct(A, B int) (int, int) {
|
||||
return A+B, A*B
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
y := 4
|
||||
|
||||
xPLUSy, xTIMESy := SumAndProduct(x, y)
|
||||
|
||||
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
|
||||
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
|
||||
}
|
||||
|
||||
Above example return two values without name, and you can name them also. If we named return values, we just use `return` to return values is fine because they initializes in the function automatically. Notice that if your functions are going to be used outside the package, which means your functions name start with capital letter, you'd better write complete statement for `return`; it makes your code more readable.
|
||||
|
||||
func SumAndProduct(A, B int) (add int, Multiplied int) {
|
||||
add = A+B
|
||||
Multiplied = A*B
|
||||
return
|
||||
}
|
||||
|
||||
### Variable arguments
|
||||
|
||||
Go supports variable arguments, which means you can give uncertain number of argument to functions.
|
||||
|
||||
func myfunc(arg ...int) {}
|
||||
|
||||
`arg …int` tells Go this is the function that has variable arguments. Notice that these arguments are type `int`. In the body of function, the `arg` becomes a `slice` of `int`.
|
||||
|
||||
for _, n := range arg {
|
||||
fmt.Printf("And the number is: %d\n", n)
|
||||
}
|
||||
|
||||
### Pass by value and pointers
|
||||
|
||||
When we pass an argument to the function that was called, that function actually gets the copy of our variables, any change will not affect to the original variable.
|
||||
|
||||
Let's see one example to prove my words.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
// simple function to add 1 to a
|
||||
func add1(a int) int {
|
||||
a = a+1 // we change value of a
|
||||
return a // return new value of a
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
|
||||
fmt.Println("x = ", x) // should print "x = 3"
|
||||
|
||||
x1 := add1(x) // call add1(x)
|
||||
|
||||
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
|
||||
fmt.Println("x = ", x) // should print "x = 3"
|
||||
}
|
||||
|
||||
Did you see that? Even though we called `add1`, and `add1` adds one to `a`, the value of `x` doesn't change.
|
||||
|
||||
The reason is very simple: when we called `add1`, we gave a copy of `x` to it, not the `x` itself.
|
||||
|
||||
Now you may ask how I can pass the real `x` to the function.
|
||||
|
||||
We need use pointers here. We know variables store in the memory, and they all have memory address, we change value of variable is to change the value in that variable's memory address. Therefore the function `add1` have to know the memory address of `x` in order to change its value. Here we pass `&x` to the function, and change argument's type to pointer type `*int`. Be aware that we pass a copy of pointer, not copy of value.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
// simple function to add 1 to a
|
||||
func add1(a *int) int {
|
||||
*a = *a+1 // we changed value of a
|
||||
return *a // return new value of a
|
||||
}
|
||||
|
||||
func main() {
|
||||
x := 3
|
||||
|
||||
fmt.Println("x = ", x) // should print "x = 3"
|
||||
|
||||
x1 := add1(&x) // call add1(&x) pass memory address of x
|
||||
|
||||
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
|
||||
fmt.Println("x = ", x) // should print "x = 4"
|
||||
}
|
||||
|
||||
Now we can change value of `x` in the functions. Why we use pointers? What are the advantages?
|
||||
|
||||
- Use more functions to operate one variable.
|
||||
- Low cost by passing memory addresses (8 bytes), copy is not an efficient way in both time and space to pass variables.
|
||||
- `string`, `slice`, `map` are reference types, so they use pointers when pass to functions as default. (Attention: If you need to change length of `slice`, you have to pass pointers explicitly)
|
||||
|
||||
### defer
|
||||
|
||||
Go has a good design called `defer`, you can have many `defer` statements in one function; they will execute by reverse order when the program executes to the end of functions. Especially when the program open some resource files, these files have to be closed before the function return with errors. Let's see some examples.
|
||||
|
||||
func ReadWrite() bool {
|
||||
file.Open("file")
|
||||
// Do some work
|
||||
if failureX {
|
||||
file.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
if failureY {
|
||||
file.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
file.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
We saw some code repeat several times, `defer` solves this problem very well. It doesn't only help you make clean code, and also make code more readable.
|
||||
|
||||
func ReadWrite() bool {
|
||||
file.Open("file")
|
||||
defer file.Close()
|
||||
if failureX {
|
||||
return false
|
||||
}
|
||||
if failureY {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
If there are more than one `defer`, they will execute by reverse order. The following example will print `4 3 2 1 0`.
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
defer fmt.Printf("%d ", i)
|
||||
}
|
||||
|
||||
### Functions as values and types
|
||||
|
||||
Functions are also variables in Go, we can use `type` to define them. Functions that have same signature can be seen as same type.
|
||||
|
||||
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
|
||||
|
||||
What's the advantage of this feature? So that we can pass functions as values.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type testInt func(int) bool // define a function type of variable
|
||||
|
||||
func isOdd(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isEven(integer int) bool {
|
||||
if integer%2 == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pass the function `f` as an argument to another function
|
||||
|
||||
func filter(slice []int, f testInt) []int {
|
||||
var result []int
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func main(){
|
||||
slice := []int {1, 2, 3, 4, 5, 7}
|
||||
fmt.Println("slice = ", slice)
|
||||
odd := filter(slice, isOdd) // use function as values
|
||||
fmt.Println("Odd elements of slice are: ", odd)
|
||||
even := filter(slice, isEven)
|
||||
fmt.Println("Even elements of slice are: ", even)
|
||||
}
|
||||
|
||||
It's very useful when we use interfaces. As you can see `testInt` is a variable that has function type, and return values and arguments of `filter` are the same as `testInt`. Therefore, we have more complex logic in our programs, and make code more flexible.
|
||||
|
||||
### Panic and Recover
|
||||
|
||||
Go doesn't have `try-catch` structure like Java does. Instead of throwing exceptions, Go uses `panic` and `recover` to deal with errors. However, you shouldn't use `panic` very much, although it's powerful.
|
||||
|
||||
Panic is a built-in function to break normal flow of programs and get into panic status. When function `F` called `panic`, function `F` will not continue executing, but its `defer` functions are always executing. Then `F` goes back to its break point where causes panic status. The program will not end until all the functions return with panic to the first level of that `goroutine`. `panic` can be produced by calling `panic` in the program, and some errors also cause `panic` like array access out of bounds.
|
||||
|
||||
Recover is a built-in function to recover `goroutine` from panic status, only call `recover` in `defer` functions is useful because normal functions will not be executed when the program is in the panic status. It catches `panic` value if the program is in the panic status, it gets `nil` if the program is not in panic status.
|
||||
|
||||
The following example shows how to use `panic`.
|
||||
|
||||
var user = os.Getenv("USER")
|
||||
|
||||
func init() {
|
||||
if user == "" {
|
||||
panic("no value for $USER")
|
||||
}
|
||||
}
|
||||
|
||||
The following example shows how to check `panic`.
|
||||
|
||||
func throwsPanic(f func()) (b bool) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
b = true
|
||||
}
|
||||
}()
|
||||
f() // if f causes panic, it will recover
|
||||
return
|
||||
}
|
||||
|
||||
### `main` function and `init` function
|
||||
|
||||
Go has two retention which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. these two function are not able to have arguments or return values. Even though we can write many `init` function in one package, I strongly recommend to write only one `init` function for each package.
|
||||
|
||||
Go programs will call `init()` and `main()` automatically, so you don't need to call them by yourself. For every package, function `init` is optional, but `package main` has one and only one `main` function.
|
||||
|
||||
Programs initializes and executes from `main` package, if `main` package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After imported packages, programs will initialize constants and variables in imported packages, then execute `init` function if it exists, and so on. After all the other packages were initialized, programs start initialize constants and variables in `main` package, then execute `init` function in package if it exists. The following figure shows the process.
|
||||
|
||||

|
||||
|
||||
Figure 2.6 Flow of programs initialization in Go
|
||||
|
||||
### import
|
||||
|
||||
We use `import` very often in Go programs as follows.
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
)
|
||||
|
||||
Then we use functions in that package as follows.
|
||||
|
||||
fmt.Println("hello world")
|
||||
|
||||
`fmt` is from Go standard library, it locates in $GOROOT/pkg. Go uses two ways to support third-party packages.
|
||||
|
||||
1. Relative path
|
||||
import "./model" // load package in the same directory, I don't recommend this way.
|
||||
2. Absolute path
|
||||
import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model"
|
||||
|
||||
There are some special operators when we import packages, and beginners are always confused by these operators.
|
||||
|
||||
1. Dot operator.
|
||||
Sometime we see people use following way to import packages.
|
||||
|
||||
import(
|
||||
. "fmt"
|
||||
)
|
||||
|
||||
The dot operator means you can omit package name when you call functions in that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`.
|
||||
2. Alias operation.
|
||||
It changes the name of package that we imported when we call functions in that package.
|
||||
|
||||
import(
|
||||
f "fmt"
|
||||
)
|
||||
|
||||
Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`.
|
||||
3. `_` operator.
|
||||
This is the operator that hard to understand whitout someone explanning to you.
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
|
||||
The `_` operator actually means we just import that package, and use `init` function in that package, and we are not sure if want to use functions in that package.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Go foundation](02.2.md)
|
||||
- Next section: [struct](02.4.md)
|
||||
214
en/eBook/02.4.md
Normal file
214
en/eBook/02.4.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 2.4 struct
|
||||
|
||||
## struct
|
||||
|
||||
We can define new type of container of other properties or fields in Go like in other programming languages. For example, we can create a type called `person` to represent a person, this type has name and age. We call this kind of type as `struct`.
|
||||
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
Look how easy it is to define a `struct`!
|
||||
|
||||
There are two fields.
|
||||
|
||||
- `name` is a `string` used to store a person's name.
|
||||
- `age` is a `int` used to store a person's age.
|
||||
|
||||
Let's see how to use it.
|
||||
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
var P person // p is person type
|
||||
|
||||
P.name = "Astaxie" // assign "Astaxie" to the filed 'name' of p
|
||||
P.age = 25 // assign 25 to field 'age' of p
|
||||
fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p
|
||||
|
||||
There are three more ways to define struct.
|
||||
|
||||
- Assign initial values by order
|
||||
|
||||
P := person{"Tom", 25}
|
||||
|
||||
- Use format `field:value` to initialize without order
|
||||
|
||||
P := person{age:24, name:"Bob"}
|
||||
|
||||
- Define a anonymous struct, then initialize it
|
||||
|
||||
P := struct{name string; age int}{"Amy",18}
|
||||
|
||||
Let's see a complete example.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
// define a new type
|
||||
type person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
// compare age of two people, return the older person and differences of age
|
||||
// struct is passed by value
|
||||
func Older(p1, p2 person) (person, int) {
|
||||
if p1.age>p2.age {
|
||||
return p1, p1.age-p2.age
|
||||
}
|
||||
return p2, p2.age-p1.age
|
||||
}
|
||||
|
||||
func main() {
|
||||
var tom person
|
||||
|
||||
// initialization
|
||||
tom.name, tom.age = "Tom", 18
|
||||
|
||||
// initialize two values by format "field:value"
|
||||
bob := person{age:25, name:"Bob"}
|
||||
|
||||
// initialize two values with order
|
||||
paul := person{"Paul", 43}
|
||||
|
||||
tb_Older, tb_diff := Older(tom, bob)
|
||||
tp_Older, tp_diff := Older(tom, paul)
|
||||
bp_Older, bp_diff := Older(bob, paul)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff)
|
||||
|
||||
fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff)
|
||||
}
|
||||
|
||||
### embedded fields in struct
|
||||
|
||||
I just introduced you how to define a struct with fields name and type. In fact, Go support fields without name but types, we call these embedded fields.
|
||||
|
||||
When the embedded field is a struct, all the fields in that struct will be the fields in the new struct implicitly.
|
||||
|
||||
Let's see one example.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // embedded field, it means Student struct includes all fields that Human has.
|
||||
speciality string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// initialize a student
|
||||
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
|
||||
|
||||
// access fields
|
||||
fmt.Println("His name is ", mark.name)
|
||||
fmt.Println("His age is ", mark.age)
|
||||
fmt.Println("His weight is ", mark.weight)
|
||||
fmt.Println("His speciality is ", mark.speciality)
|
||||
// modify notes
|
||||
mark.speciality = "AI"
|
||||
fmt.Println("Mark changed his speciality")
|
||||
fmt.Println("His speciality is ", mark.speciality)
|
||||
// modify age
|
||||
fmt.Println("Mark become old")
|
||||
mark.age = 46
|
||||
fmt.Println("His age is", mark.age)
|
||||
// modify weight
|
||||
fmt.Println("Mark is not an athlet anymore")
|
||||
mark.weight += 60
|
||||
fmt.Println("His weight is", mark.weight)
|
||||
}
|
||||
|
||||

|
||||
|
||||
Figure 2.7 Inheritance in Student and Human
|
||||
|
||||
We see that we access age and name in Student just like we access them in Human. This is how embedded field works. It is very cool, isn't it? Hold on, there is something cooler! You can even use Student to access Human this embedded field!
|
||||
|
||||
mark.Human = Human{"Marcus", 55, 220}
|
||||
mark.Human.age -= 1
|
||||
|
||||
All the types can be used as embedded fields.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Skills []string
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
weight int
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // struct as embedded field
|
||||
Skills // string slice as embedded field
|
||||
int // built-in type as embedded field
|
||||
speciality string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// initialize Student Jane
|
||||
jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
|
||||
// access fields
|
||||
fmt.Println("Her name is ", jane.name)
|
||||
fmt.Println("Her age is ", jane.age)
|
||||
fmt.Println("Her weight is ", jane.weight)
|
||||
fmt.Println("Her speciality is ", jane.speciality)
|
||||
// modify value of skill field
|
||||
jane.Skills = []string{"anatomy"}
|
||||
fmt.Println("Her skills are ", jane.Skills)
|
||||
fmt.Println("She acquired two new ones ")
|
||||
jane.Skills = append(jane.Skills, "physics", "golang")
|
||||
fmt.Println("Her skills now are ", jane.Skills)
|
||||
// modify embedded field
|
||||
jane.int = 3
|
||||
fmt.Println("Her preferred number is", jane.int)
|
||||
}
|
||||
|
||||
In above example we can see that all types can be embedded fields and we can use functions to operate them.
|
||||
|
||||
There is one more problem, if Human has a field called `phone` and Student has a field with same name, what should we do?
|
||||
|
||||
Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access `student.phone`, we will get the field called phone in student, not in the Human struct. This feature can be simply seen as `overload` of field.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string // Human has phone field
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human // embedded field Human
|
||||
speciality string
|
||||
phone string // phone in employee
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
|
||||
fmt.Println("Bob's work phone is:", Bob.phone)
|
||||
// access phone field in Human
|
||||
fmt.Println("Bob's personal phone is:", Bob.Human.phone)
|
||||
}
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Control statements and functions](02.3.md)
|
||||
- Next section: [Object-oriented](02.5.md)
|
||||
307
en/eBook/02.5.md
Normal file
307
en/eBook/02.5.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Object-oriented
|
||||
|
||||
We talked about functions and structs in the last two sections, did you ever think about using functions as fields of a struct? In this section, I will introduce you another form of method that has receiver, which is called `method`.
|
||||
|
||||
## method
|
||||
|
||||
Suppose you define a struct of rectangle, and you want to calculate its area, we usually use following code to achieve this goal.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
func area(r Rectangle) float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
fmt.Println("Area of r1 is: ", area(r1))
|
||||
fmt.Println("Area of r2 is: ", area(r2))
|
||||
}
|
||||
|
||||
Above example can calculate rectangle's area, we use the function called `area`, but it's not a method of a rectangle struct (like methods in class in classic Object-oriented language). The function and struct are two independent things as you may notice.
|
||||
|
||||
It's not a problem so far. What if you also have to calculate area of circle, square, pentagon, even more, you are going to add more functions with very similar name.
|
||||
|
||||

|
||||
|
||||
Figure 2.8 Relationship between function and struct
|
||||
|
||||
Obviously, it's not cool. Also the area should be the property of circle or rectangle.
|
||||
|
||||
For those reasons, we have concepts about `method`. `method` is affiliated of type, it has same syntax as function except one more thing after the keyword `func` that is called `receiver` which is the main body of that method.
|
||||
|
||||
Use the same example, `Rectangle.area()` belongs to rectangle, not as a peripheral function. More specifically, `length`, `width` and `area()` all belong to rectangle.
|
||||
|
||||
As Rob Pike said.
|
||||
|
||||
"A method is a function with an implicit first argument, called a receiver."
|
||||
|
||||
Syntax of method.
|
||||
|
||||
func (r ReceiverType) funcName(parameters) (results)
|
||||
|
||||
Let's change out example by using method.
|
||||
|
||||
package main
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
width, height float64
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
radius float64
|
||||
}
|
||||
|
||||
func (r Rectangle) area() float64 {
|
||||
return r.width*r.height
|
||||
}
|
||||
|
||||
func (c Circle) area() float64 {
|
||||
return c.radius * c.radius * math.Pi
|
||||
}
|
||||
|
||||
func main() {
|
||||
r1 := Rectangle{12, 2}
|
||||
r2 := Rectangle{9, 4}
|
||||
c1 := Circle{10}
|
||||
c2 := Circle{25}
|
||||
|
||||
fmt.Println("Area of r1 is: ", r1.area())
|
||||
fmt.Println("Area of r2 is: ", r2.area())
|
||||
fmt.Println("Area of c1 is: ", c1.area())
|
||||
fmt.Println("Area of c2 is: ", c2.area())
|
||||
}
|
||||
|
||||
Notes for using methods.
|
||||
|
||||
- If the name of methods is same, but they don't have same receivers, they are not same.
|
||||
- methods are able to access fields in receivers.
|
||||
- Use `.` to call a method in the struct, just like to call fields.
|
||||
|
||||

|
||||
|
||||
Figure 2.9 Methods are difference in difference struct
|
||||
|
||||
In above example, method area() is respectively belonging to Rectangle and Circle, so the receivers are Rectangle and Circle.
|
||||
|
||||
One thing is worthy of note that the method with a dotted line means the receiver is passed by value, not by reference. The different between them is that the method could change receiver's value when the receiver is passed by reference, and it gets the copy of receiver when the receiver is passed by value.
|
||||
|
||||
Does the receiver can only be struct? Of course not, any type could be the receiver of a method. You may be confused about customized type, struct is a special type of customized type, there are more customized types.
|
||||
|
||||
Use following format to define a customized type.
|
||||
|
||||
type typeName typeLiteral
|
||||
|
||||
Examples about customized type.
|
||||
|
||||
type ages int
|
||||
|
||||
type money float32
|
||||
|
||||
type months map[string]int
|
||||
|
||||
m := months {
|
||||
"January":31,
|
||||
"February":28,
|
||||
...
|
||||
"December":31,
|
||||
}
|
||||
|
||||
I hope you know how to use customized type now. it's like `typedef` in C, we use `ages` to substitute `int` in above example.
|
||||
|
||||
Let's get back to `method`.
|
||||
|
||||
You can use as many methods in customized types as you want.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
const(
|
||||
WHITE = iota
|
||||
BLACK
|
||||
BLUE
|
||||
RED
|
||||
YELLOW
|
||||
)
|
||||
|
||||
type Color byte
|
||||
|
||||
type Box struct {
|
||||
width, height, depth float64
|
||||
color Color
|
||||
}
|
||||
|
||||
type BoxList []Box //a slice of boxes
|
||||
|
||||
func (b Box) Volume() float64 {
|
||||
return b.width * b.height * b.depth
|
||||
}
|
||||
|
||||
func (b *Box) SetColor(c Color) {
|
||||
b.color = c
|
||||
}
|
||||
|
||||
func (bl BoxList) BiggestsColor() Color {
|
||||
v := 0.00
|
||||
k := Color(WHITE)
|
||||
for _, b := range bl {
|
||||
if b.Volume() > v {
|
||||
v = b.Volume()
|
||||
k = b.color
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (bl BoxList) PaintItBlack() {
|
||||
for i, _ := range bl {
|
||||
bl[i].SetColor(BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) String() string {
|
||||
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
|
||||
return strings[c]
|
||||
}
|
||||
|
||||
func main() {
|
||||
boxes := BoxList {
|
||||
Box{4, 4, 4, RED},
|
||||
Box{10, 10, 1, YELLOW},
|
||||
Box{1, 1, 20, BLACK},
|
||||
Box{10, 10, 1, BLUE},
|
||||
Box{10, 30, 1, WHITE},
|
||||
Box{20, 20, 20, YELLOW},
|
||||
}
|
||||
|
||||
fmt.Printf("We have %d boxes in our set\n", len(boxes))
|
||||
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
|
||||
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
|
||||
fmt.Println("Let's paint them all black")
|
||||
boxes.PaintItBlack()
|
||||
fmt.Println("The color of the second one is", boxes[1].color.String())
|
||||
|
||||
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
|
||||
}
|
||||
|
||||
We define some constants and customized types.
|
||||
|
||||
- Use `Color` as alias of `byte`.
|
||||
- Define a struct `Box` which has fields height, width, length and color.
|
||||
- Define a struct `BoxList` which has `Box` as its field.
|
||||
|
||||
Then we defined some methods for our customized types.
|
||||
|
||||
- Volume() use Box as its receiver, returns volume of Box.
|
||||
- SetColor(c Color) changes Box's color.
|
||||
- BiggestsColor() returns the color which has the biggest volume.
|
||||
- PaintItBlack() sets color for all Box in BoxList to black.
|
||||
- String() use Color as its receiver, returns the string format of color name.
|
||||
|
||||
Is it much clear when we use words to describe our requirements? We often write our requirements before we start coding.
|
||||
|
||||
### Use pointer as receiver
|
||||
|
||||
Let's take a look at method `SetColor`, its receiver is a pointer of Box. Yes, you can use `*Box` as receiver. Why we use pointer here? Because we want to change Box's color in this method, if we don't use pointer, it only changes value of copy of Box.
|
||||
|
||||
If we see receiver as the first argument of the method, it's not hard to understand how it works.
|
||||
|
||||
You may ask that we should use `(*b).Color=c` instead of `b.Color=c` in method SetColor(). Either one is OK here because Go knows it. Do you think Go is more fascinating now?
|
||||
|
||||
You may also ask we should use `(&bl[i]).SetColor(BLACK)` in `PaintItBlack` because we pass a pointer to `SetColor`. One more time, either one is OK because Go knows it!
|
||||
|
||||
### Inheritance of method
|
||||
|
||||
We learned inheritance of field in last section, and we also have inheritance of method in Go. So that if a anonymous field has methods, then the struct that contains the field have all methods from it as well.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human // anonymous field
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
}
|
||||
|
||||
// define a method in Human
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
|
||||
### Method overload
|
||||
|
||||
If we want Employee to have its own method `SayHi`, we can define the method that has same name in Employee, and it will hide `SayHi` in Human when we call it.
|
||||
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human
|
||||
school string
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
}
|
||||
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
func main() {
|
||||
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
|
||||
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
|
||||
|
||||
mark.SayHi()
|
||||
sam.SayHi()
|
||||
}
|
||||
|
||||
You are able to write an Object-oriented program now, and methods use rule of capital letter to decide whether public or private as well.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [struct](02.4.md)
|
||||
- Next section: [interface](02.6.md)
|
||||
395
en/eBook/02.6.md
Normal file
395
en/eBook/02.6.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# 2.6 Interface
|
||||
|
||||
## Interface
|
||||
|
||||
One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation.
|
||||
|
||||
### What is an interface
|
||||
|
||||
In short, an interface is a set of methods, that we use to define a set of actions.
|
||||
|
||||
Like the examples in previous sections, both Student and Employee can `SayHi()`, but they don't do the same thing.
|
||||
|
||||
Let's do more work, we add one more method `Sing()` to them, also add `BorrowMoney()` to Student and `SpendSalary()` to Employee.
|
||||
|
||||
Now Student has three methods called `SayHi()`, `Sing()`, `BorrowMoney()`, and Employee has `SayHi()`, `Sing()` and `SpendSalary()`.
|
||||
|
||||
This combination of methods is called an interface, and is implemented by Student and Employee. So Student and Employee implement the interface: `SayHi()`, `Sing()`. At the same time, Employee doesn't implement the interface: `SayHi()`, `Sing()`, `BorrowMoney()`, and Student doesn't implement the interface: `SayHi()`, `Sing()`, `SpendSalary()`. This is because Employee doesn't have the method `BorrowMoney()` and Student doesn't have the method `SpendSalary()`.
|
||||
|
||||
### Type of Interface
|
||||
|
||||
An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
func (h *Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (h *Human) Sing(lyrics string) {
|
||||
fmt.Println("La la, la la la, la la la la la...", lyrics)
|
||||
}
|
||||
|
||||
func (h *Human) Guzzle(beerStein string) {
|
||||
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
|
||||
}
|
||||
|
||||
// Employee overloads Sayhi
|
||||
func (e *Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
func (s *Student) BorrowMoney(amount float32) {
|
||||
s.loan += amount // (again and again and...)
|
||||
}
|
||||
|
||||
func (e *Employee) SpendSalary(amount float32) {
|
||||
e.money -= amount // More vodka please!!! Get me through the day!
|
||||
}
|
||||
|
||||
// define interface
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
Guzzle(beerStein string)
|
||||
}
|
||||
|
||||
type YoungChap interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
BorrowMoney(amount float32)
|
||||
}
|
||||
|
||||
type ElderlyGent interface {
|
||||
SayHi()
|
||||
Sing(song string)
|
||||
SpendSalary(amount float32)
|
||||
}
|
||||
|
||||
We know that an interface can be implemented by any type, and one type can implement many interfaces at the same time.
|
||||
|
||||
Note that any type implements the empty interface `interface{}` because it doesn't have any methods and all types have zero methods as default.
|
||||
|
||||
### Value of interface
|
||||
|
||||
So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can assigned to this variable.
|
||||
|
||||
Like the above example, if we define a variable m as interface Men, then any one of Student, Human or Employee can be assigned to m. So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
Human
|
||||
school string
|
||||
loan float32
|
||||
}
|
||||
|
||||
type Employee struct {
|
||||
Human
|
||||
company string
|
||||
money float32
|
||||
}
|
||||
|
||||
func (h Human) SayHi() {
|
||||
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
||||
}
|
||||
|
||||
func (h Human) Sing(lyrics string) {
|
||||
fmt.Println("La la la la...", lyrics)
|
||||
}
|
||||
|
||||
func (e Employee) SayHi() {
|
||||
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
|
||||
e.company, e.phone) //Yes you can split into 2 lines here.
|
||||
}
|
||||
|
||||
// Interface Men implemented by Human, Student and Employee
|
||||
type Men interface {
|
||||
SayHi()
|
||||
Sing(lyrics string)
|
||||
}
|
||||
|
||||
func main() {
|
||||
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
|
||||
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
|
||||
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
|
||||
Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
|
||||
|
||||
// define interface i
|
||||
var i Men
|
||||
|
||||
//i can store Student
|
||||
i = mike
|
||||
fmt.Println("This is Mike, a Student:")
|
||||
i.SayHi()
|
||||
i.Sing("November rain")
|
||||
|
||||
//i can store Employee
|
||||
i = Tom
|
||||
fmt.Println("This is Tom, an Employee:")
|
||||
i.SayHi()
|
||||
i.Sing("Born to be wild")
|
||||
|
||||
// slice of Men
|
||||
fmt.Println("Let's use a slice of Men and see what happens")
|
||||
x := make([]Men, 3)
|
||||
// these three elements are different types but they all implemented interface Men
|
||||
x[0], x[1], x[2] = paul, sam, mike
|
||||
|
||||
for _, value := range x {
|
||||
value.SayHi()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself.
|
||||
|
||||
### Empty interface
|
||||
|
||||
An empty interface is an interface that doesn't contain any methods, so all types implemented an empty interface. It's very useful when we want to store all types at some point, and is similar to void* in C.
|
||||
|
||||
// define a as empty interface
|
||||
var a interface{}
|
||||
var i int = 5
|
||||
s := "Hello world"
|
||||
// a can store value of any type
|
||||
a = i
|
||||
a = s
|
||||
|
||||
If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty as its return value type, it can return any type.
|
||||
|
||||
### Method arguments of an interface
|
||||
|
||||
Any variable that can be used in an interface, so we can think about how can we use this feature to pass any type of variable to the function.
|
||||
|
||||
For example, we use fmt.Println a lot, but have you ever noticed that it accepts any type of arguments? Looking at the open source code of fmt, we see the following definition.
|
||||
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Human struct {
|
||||
name string
|
||||
age int
|
||||
phone string
|
||||
}
|
||||
|
||||
// Human implemented fmt.Stringer
|
||||
func (h Human) String() string {
|
||||
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
|
||||
}
|
||||
|
||||
func main() {
|
||||
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
||||
fmt.Println("This Human is : ", Bob)
|
||||
}
|
||||
|
||||
|
||||
Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format.
|
||||
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
||||
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
||||
|
||||
Attention: If the type implemented the interface `error`, fmt will call `error()`, so you don't have to implement Stringer at this point.
|
||||
|
||||
### Type of variable in an interface
|
||||
|
||||
If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways that I'm going to tell you.
|
||||
|
||||
- Assertion of Comma-ok pattern
|
||||
|
||||
Go has the syntax `value, ok := element.(T)`. This checks to see if the variable is the type that we expect, where the value is the value of the variable, ok is a variable of boolean type, element is the interface variable and the T is the type of assertion.
|
||||
|
||||
If the element is the type that we expect, ok will be true, false otherwise.
|
||||
|
||||
Let's use an example to see more clearly.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List []Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
func (p Person) String() string {
|
||||
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
||||
}
|
||||
|
||||
func main() {
|
||||
list := make(List, 3)
|
||||
list[0] = 1 // an int
|
||||
list[1] = "Hello" // a string
|
||||
list[2] = Person{"Dennis", 70}
|
||||
|
||||
for index, element := range list {
|
||||
if value, ok := element.(int); ok {
|
||||
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
||||
} else if value, ok := element.(string); ok {
|
||||
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
||||
} else if value, ok := element.(Person); ok {
|
||||
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
||||
} else {
|
||||
fmt.Println("list[%d] is of a different type", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It's quite easy to use this pattern, but if we have many types to test, we'd better use `switch`.
|
||||
|
||||
- switch test
|
||||
|
||||
Let's use `switch` to rewrite the above example.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Element interface{}
|
||||
type List []Element
|
||||
|
||||
type Person struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
func (p Person) String() string {
|
||||
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
||||
}
|
||||
|
||||
func main() {
|
||||
list := make(List, 3)
|
||||
list[0] = 1 //an int
|
||||
list[1] = "Hello" //a string
|
||||
list[2] = Person{"Dennis", 70}
|
||||
|
||||
for index, element := range list {
|
||||
switch value := element.(type) {
|
||||
case int:
|
||||
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
||||
case string:
|
||||
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
||||
case Person:
|
||||
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
||||
default:
|
||||
fmt.Println("list[%d] is of a different type", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
One thing you should remember is that `element.(type)` cannot be used outside of `switch` body, which means in that case you have you use pattern `comma-ok`.
|
||||
|
||||
### Embedded interfaces
|
||||
|
||||
The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them `Embedded interfaces`. Here, we follows the same rules as anonymous fields. More specifically, if an interface has another interface as the embedded interface, it will have all the methods that the embedded interface has.
|
||||
|
||||
We can see source file in `container/heap` has one definition as follows.
|
||||
|
||||
type Interface interface {
|
||||
sort.Interface // embedded sort.Interface
|
||||
Push(x interface{}) //a Push method to push elements into the heap
|
||||
Pop() interface{} //a Pop elements that pops elements from the heap
|
||||
}
|
||||
|
||||
We see that `sort.Interface` is an embedded interface, so the above Interface has three methods that in `sort.Interface` implicitly.
|
||||
|
||||
type Interface interface {
|
||||
// Len is the number of elements in the collection.
|
||||
Len() int
|
||||
// Less returns whether the element with index i should sort
|
||||
// before the element with index j.
|
||||
Less(i, j int) bool
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
Swap(i, j int)
|
||||
}
|
||||
|
||||
Another example is the `io.ReadWriter` in package `io`.
|
||||
|
||||
// io.ReadWriter
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
### Reflection
|
||||
|
||||
Reflection in Go is used for determining information at runtime. We use the `reflect` package, and this official [article](http://golang.org/doc/articles/laws_of_reflection.html) explains how reflect works in Go.
|
||||
|
||||
There are three steps to use reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).
|
||||
|
||||
t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements
|
||||
v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value
|
||||
|
||||
After that, we convert reflect types to get values that we need.
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Println("type:", v.Type())
|
||||
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
||||
fmt.Println("value:", v.Float())
|
||||
|
||||
Finally, if we want to change a value that is from reflect types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile.
|
||||
|
||||
var x float64 = 3.4
|
||||
v := reflect.ValueOf(x)
|
||||
v.SetFloat(7.1)
|
||||
|
||||
Instead, we must use the following code to change value from reflect types.
|
||||
|
||||
var x float64 = 3.4
|
||||
p := reflect.ValueOf(&x)
|
||||
v := p.Elem()
|
||||
v.SetFloat(7.1)
|
||||
|
||||
I just talked about basic knowledge about reflection, you must pratice more to understand more.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Object-oriented](02.5.md)
|
||||
- Next section: [Concurrency](02.7.md)
|
||||
242
en/eBook/02.7.md
Normal file
242
en/eBook/02.7.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Concurrency
|
||||
|
||||
It is said that Go is the C language of 21st century. I think there are two reasons: first, Go is a simple language; second, concurrency is a hot topic in today's world, and Go supports this feature at the language level.
|
||||
|
||||
## goroutine
|
||||
|
||||
goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. More than a dozen goroutines maybe only have 5 or 6 underlying threads. Go also gives you full support to share memory in your goroutines. One goroutine usually uses 4~5 KB stack memory. Therefore, it's not hard to run thousands of goroutines in one computer. A goroutine is more lightweight, more efficient, and more convenient than system threads.
|
||||
|
||||
goroutines run on the thread manager at runtime in Go. We use keyword `go` to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ).
|
||||
|
||||
go hello(a, b, c)
|
||||
|
||||
Let's see an example.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world") // create a new goroutine
|
||||
say("hello") // current goroutine
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
|
||||
We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data.
|
||||
|
||||
runtime.Gosched() means let the CPU execute other goroutines, and come back at some point.
|
||||
|
||||
The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to use parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
|
||||
|
||||
## channels
|
||||
|
||||
goroutines are running in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. `channel` is like a two-way pipeline in Unix shell: use channel to send or receive data. The only data type that can be used in channels is the type `channel` and keyword `chan`. Be aware that you have to use `make` to create a new `channel`.
|
||||
|
||||
ci := make(chan int)
|
||||
cs := make(chan string)
|
||||
cf := make(chan interface{})
|
||||
|
||||
channel uses the operator `<-` to send or receive data.
|
||||
|
||||
ch <- v // send v to channel ch.
|
||||
v := <-ch // receive data from ch, and assign to v
|
||||
|
||||
Let's see more examples.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x + y)
|
||||
}
|
||||
|
||||
Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that the goroutine will not continue when it receives data from an empty channel (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue when it sends data to channel (`ch<-5`) until this data is received.
|
||||
|
||||
## Buffered channels
|
||||
|
||||
I introduced non-buffered channels above, and Go also has buffered channels that can store more than one element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it.
|
||||
|
||||
ch := make(chan type, n)
|
||||
|
||||
n == 0 ! non-buffer(block)
|
||||
n > 0 ! buffer(non-block until n elements in the channel)
|
||||
|
||||
You can try the following code on your computer and change some values.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
|
||||
## Range and Close
|
||||
|
||||
We can use range to operate on buffer channels as in slice and map.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x + y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
`for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel, you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed.
|
||||
|
||||
Remember to always close channel in producers, not in consumers, or it's very easy to get into panic status.
|
||||
|
||||
Another thing you need to remember is that channels are unlike files, and you don't have to close them frequently, unless you are sure the channel is completely useless, or you want to exit range loops.
|
||||
|
||||
## Select
|
||||
|
||||
In the above examples, we only use one channel, but how can we deal with more than one channel? Go has a keyword called `select` to listen to many channels.
|
||||
|
||||
`select` is blocking by default, and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x + y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
|
||||
`select` has `default` as well, just like `switch`. When all the channels are not ready to use, it executes default (doesn't wait for channel anymore).
|
||||
|
||||
select {
|
||||
case i := <-c:
|
||||
// use i
|
||||
default:
|
||||
// executes here when c is blocked
|
||||
}
|
||||
|
||||
## Timeout
|
||||
|
||||
Sometimes a goroutine is blocked, but how can we avoid this to prevent the whole program blocking? We can set a timeout in select to do this.
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <- c:
|
||||
println(v)
|
||||
case <- time.After(5 * time.Second):
|
||||
println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
<- o
|
||||
}
|
||||
|
||||
## Runtime goroutine
|
||||
|
||||
The package `runtime` has some functions to deal with goroutines.
|
||||
|
||||
- `runtime.Goexit()`
|
||||
|
||||
Exits the current goroutine, but defer functions will be executed as usual.
|
||||
|
||||
- `runtime.Gosched()`
|
||||
|
||||
Lets the scheduler executes other goroutines, and comes back at some point.
|
||||
|
||||
- `runtime.NumCPU() int`
|
||||
|
||||
Returns number of CPU cores
|
||||
|
||||
- `runtime.NumGoroutine() int`
|
||||
|
||||
Returns number of goroutines
|
||||
|
||||
- `runtime.GOMAXPROCS(n int) int`
|
||||
|
||||
Set how many CPU cores that you want to use
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [interface](02.6.md)
|
||||
- Next section: [Summary](02.8.md)
|
||||
32
en/eBook/02.8.md
Normal file
32
en/eBook/02.8.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 2.8 Summary
|
||||
|
||||
In this chapter, we mainly introduced the 25 Go keywords. Let's review what they are and what they do.
|
||||
|
||||
break default func interface select
|
||||
case defer go map struct
|
||||
chan else goto package switch
|
||||
const fallthrough if range type
|
||||
continue for import return var
|
||||
|
||||
- `var` and `const` are used to define variables and constants.
|
||||
- `package` and `import` are for package use.
|
||||
- `func` is used to define functions and methods.
|
||||
- `return` is used to return values in functions or methods.
|
||||
- `defer` is used to define defer functions.
|
||||
- `go` is used to start a new goroutine.
|
||||
- `select` is used to switch over multiple channels for communication.
|
||||
- `interface` is used to define interfaces.
|
||||
- `struct` is used to define special customized types.
|
||||
- `break`, `case`, `continue`, `for`, `fallthrough`, `else`, `if`, `switch`, `goto`, `default` were introduced in section 2.3.
|
||||
- `chan` is the type of channel for communication among goroutines.
|
||||
- `type` is used to define customized types.
|
||||
- `map` is used to define map which is like hash table in others languages.
|
||||
- `range` is used for reading data from `slice`, `map` and `channel`.
|
||||
|
||||
If you understand how to use these 25 keywords, you've learned a lot of Go already.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Concurrency](02.7.md)
|
||||
- Next chapter: [Web foundation](03.0.md)
|
||||
9
en/eBook/03.0.md
Normal file
9
en/eBook/03.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
#3 Web foundation
|
||||
|
||||
The reason you are reading this book is that you want to learn to build web applications in Go. As I said before, Go provides many powerful packages like `http`. It helps you a lot when you build web applications. I'll teach you everything you should know in following chapters, and we'll talk about some concepts of the web and how to run web applications in Go in this chapter.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous chapter: [Chapter 2 Summary](02.8.md)
|
||||
- Next section: [Web working principles](03.1.md)
|
||||
150
en/eBook/03.1.md
Normal file
150
en/eBook/03.1.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Web working principles
|
||||
|
||||
Every time you open your browsers, type some URLs and press enter, then you will see the beautiful web pages appear on you screen. But do you know what is happening behind this simple action?
|
||||
|
||||
Normally, your browser is a client, after you typed URL, it sends requests to DNS server, get IP address of the URL. Then it finds the server in that IP address, asks to setup TCP connections. When the browser finished sending HTTP requests, server starts handling your request packages, then return HTTP response packages to your browser. Finally, the browser renders bodies of the web pages, and disconnects from the server.
|
||||
|
||||

|
||||
|
||||
Figure 3.1 Processes of users visit a website
|
||||
|
||||
A web server also known as a HTTP server, it uses HTTP protocol to communicate with clients. All web browsers can be seen as clients.
|
||||
|
||||
We can divide web working principles to following steps:
|
||||
|
||||
- Client uses TCP/IP protocol to connect to server.
|
||||
- Client sends HTTP request packages to server.
|
||||
- Server returns HTTP response packages to client, if request resources including dynamic scripts, server calls script engine first.
|
||||
- Client disconnects from server, starts rendering HTML.
|
||||
|
||||
This a simple work flow of HTTP affairs, notice that every time server closes connections after sent data to clients, and waits for next request.
|
||||
|
||||
## URL and DNS resolution
|
||||
|
||||
We are always using URL to access web pages, but do you know how URL works?
|
||||
|
||||
The full name of URL is Uniform Resource Locator, this is for describing resources on the internet. Its basic form as follows.
|
||||
|
||||
scheme://host[:port#]/path/.../[?query-string][#anchor]
|
||||
scheme assign underlying protocol(such as HTTP, HTTPS, ftp)
|
||||
host IP or domain name of HTTP server
|
||||
port# default port is 80, and you can omit in this case. If you want to use other ports, you must to specify which port. For example, http://www.cnblogs.com:8080/
|
||||
path resources path
|
||||
query-string data are sent to server
|
||||
anchor anchor
|
||||
|
||||
DNS is abbreviation of Domain Name System, it's the name system for computer network services, it converts domain name to actual IP addresses, just like a translator.
|
||||
|
||||

|
||||
|
||||
Figure 3.2 DNS working principles
|
||||
|
||||
To understand more about its working principle, let's see detailed DNS resolution process as follows.
|
||||
|
||||
1. After typed domain name `www.qq.com` in the browser, operating system will check if there is any mapping relationship in the hosts file for this domain name, if so then finished the domain name resolution.
|
||||
2. If no mapping relationship in the hosts file, operating system will check if there is any cache in the DNS, if so then finished the domain name resolution.
|
||||
3. If no mapping relationship in the hosts and DNS cache, operating system finds the first DNS resolution server in your TCP/IP setting, which is local DNS server at this time. When local DNS server received query, if the domain name that you want to query is contained in the local configuration of regional resources, then gives back results to the client. This DNS resolution is authoritative.
|
||||
4. If local DNS server doesn't contain the domain name, and there is a mapping relationship in the cache, local DNS server gives back this result to client. This DNS resolution is not authoritative.
|
||||
5. If local DNS server cannot resolve this domain name either by configuration of regional resource or cache, it gets into next step depends on the local DNS server setting. If the local DNS server doesn't enable forward mode, it sends request to root DNS server, then returns the IP of top level DNS server may know this domain name, `.com` in this case. If the first top level DNS server doesn't know, it sends request to next top level DNS server until the one that knows the domain name. Then the top level DNS server asks next level DNS server for `qq.com`, then finds the `www.qq.com` in some servers.
|
||||
6. If the local DNS server enabled forward mode, it sends request to upper level DNS server, if the upper level DNS server also doesn't know the domain name, then keep sending request to upper level. Whether local DNS server enables forward mode, server's IP address of domain name returns to local DNS server, and local server sends it to clients.
|
||||
|
||||

|
||||
|
||||
Figure 3.3 DNS resolution work flow
|
||||
|
||||
`Recursive query process` means the enquirers are changing in the process, and enquirers do not change in `Iterative query process`.
|
||||
|
||||
Now we know clients get IP addresses in the end, so the browsers are communicating with servers through IP addresses.
|
||||
|
||||
## HTTP protocol
|
||||
|
||||
HTTP protocol is the core part of web services. It's important to know what is HTTP protocol before you understand how web works.
|
||||
|
||||
HTTP is the protocol that used for communicating between browsers and web servers, it is based on TCP protocol, and usually use port 80 in the web server side. It is a protocol that uses request-response model, clients send request and servers response. According to HTTP protocol, clients always setup a new connection and send a HTTP request to server in every affair. Server is not able to connect to client proactively, or a call back connection. The connection between the client and the server can be closed by any side. For example, you can cancel your download task and HTTP connection. It disconnects from server before you finish downloading.
|
||||
|
||||
HTTP protocol is stateless, which means the server has no idea about the relationship between two connections, even though they are both from same client. To solve this problem, web applications use Cookie to maintain sustainable state of connections.
|
||||
|
||||
Because HTTP protocol is based on TCP protocol, so all TCP attacks will affect the HTTP communication in your server, such as SYN Flood, DoS and DDoS.
|
||||
|
||||
### HTTP request package (browser information)
|
||||
|
||||
Request packages all have three parts: request line, request header, and body. There is one blank line between header and body.
|
||||
|
||||
GET /domains/example/ HTTP/1.1 // request line: request method, URL, protocol and its version
|
||||
Host:www.iana.org // domain name
|
||||
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 // browser information
|
||||
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 // mine that clients can accept
|
||||
Accept-Encoding:gzip,deflate,sdch // stream compression
|
||||
Accept-Charset:UTF-8,*;q=0.5 // character set in client side
|
||||
// blank line
|
||||
// body, request resource arguments (for example, arguments in POST)
|
||||
|
||||
We use fiddler to get following request information.
|
||||
|
||||

|
||||
|
||||
Figure 3.4 Information of GET method caught by fiddler
|
||||
|
||||

|
||||
|
||||
Figure 3.5 Information of POST method caught by fiddler
|
||||
|
||||
**We can see that GET method doesn't have request body that POST does.**
|
||||
|
||||
There are many methods you can use to communicate with servers in HTTP, and GET, POST, PUT, DELETE are the basic 4 methods that we use. A URL represented a resource on the network, so these 4 method means query, change, add and delete operations. GET and POST are most commonly used in HTTP. GET appends data to the URL and uses `?` to break up them, uses `&` between arguments, like `EditPosts.aspx?name=test1&id=123456`. POST puts data in the request body because URL has length limitation by browsers, so POST can submit much more data than GET method. Also when we submit our user name and password, we don't want this kind of information appear in the URL, so we use POST to keep them invisible.
|
||||
|
||||
### HTTP response package (server information)
|
||||
|
||||
Let's see what information is contained in the response packages.
|
||||
|
||||
HTTP/1.1 200 OK // status line
|
||||
Server: nginx/1.0.8 // web server software and its version in the server machine
|
||||
Date:Date: Tue, 30 Oct 2012 04:14:25 GMT // responded time
|
||||
Content-Type: text/html // responded data type
|
||||
Transfer-Encoding: chunked // it means data were sent in fragments
|
||||
Connection: keep-alive // keep connection
|
||||
Content-Length: 90 // length of body
|
||||
// blank line
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... // message body
|
||||
|
||||
The first line is called status line, it has HTTP version, status code and statue message.
|
||||
|
||||
Status code tells the client is HTTP server has expectation response. In HTTP/1.1, we defined 5 kinds of status code.
|
||||
|
||||
- 1xx Informational
|
||||
- 2xx Success
|
||||
- 3xx Redirection
|
||||
- 4xx Client Error
|
||||
- 5xx Server Error
|
||||
|
||||
Let's see more examples about response packages, 200 means server responded correctly, 302 means redirection.
|
||||
|
||||

|
||||
|
||||
Figure 3.6 Full information for visiting a website
|
||||
|
||||
### HTTP is stateless and Connection: keep-alive
|
||||
|
||||
Stateless doesn't means server has no ability to keep a connection, in other words, server doesn't know any relationship between any two requests.
|
||||
|
||||
In HTTP/1.1, Keep-alive is used as default, if clients have more requests, they will use the same connection for many different requests.
|
||||
|
||||
Notice that Keep-alive cannot keep one connection forever, the software runs in the server has certain time to keep connection, and you can change it.
|
||||
|
||||
## Request instance
|
||||
|
||||

|
||||
|
||||
Figure 3.7 All packages for open one web page
|
||||
|
||||
We can see the whole process of communication between the client and server by above picture. You may notice that there are many resource files in the list, they are called static files, and Go has specialized processing methods for these files.
|
||||
|
||||
This is the most important function of browsers, request for a URL and get data from web servers, then render HTML for good user interface. If it finds some files in the DOM, such as CSS or JS files, browsers will request for these resources from server again, until all the resources finished rendering on your screen.
|
||||
|
||||
Reduce HTTP request times is one of the methods that improves speed of loading web pages, which is reducing CSS and JS files, it reduces pressure in the web servers at the same time.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Web foundation](03.0.md)
|
||||
- Next section: [Build a simple web server](03.2.md)
|
||||
65
en/eBook/03.2.md
Normal file
65
en/eBook/03.2.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 3.2 Build a simple web server
|
||||
|
||||
We talked about that web applications are based on HTTP protocol, and Go provides fully ability for HTTP in package `net/http`, it's very easy to setup a web server by using this package.
|
||||
|
||||
## Use http package setup a web server
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"log"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() // parse arguments, you have to call this by yourself
|
||||
fmt.Println(r.Form) // print form information in server side
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") // send data to client side
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) // set router
|
||||
err := http.ListenAndServe(":9090", nil) // set listen port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
After we executed above code, it starts listening to port 9090 in local host.
|
||||
|
||||
Open your browser and visit `http://localhost:9090`, you can see that `Hello astaxie` is on your screen.
|
||||
|
||||
Let's try another address with arguments: `http://localhost:9090/?url_long=111&url_long=222`
|
||||
|
||||
Now see what happened in both client and server sides.
|
||||
|
||||
You should see following information in your server side:
|
||||
|
||||

|
||||
|
||||
Figure 3.8 Server printed information
|
||||
|
||||
As you can see, we only need to call two functions to build a simple web server.
|
||||
|
||||
If you are working with PHP, you probably want to ask do we need something like Nginx or Apache, the answer is we don't need because Go listens to TCP port by itself, and the function `sayhelloName` is the logic function like controller in PHP.
|
||||
|
||||
If you are working with Python, you should know tornado, and the above example is very similar to that.
|
||||
|
||||
If you are working with Ruby, you may notice it is like script/server in ROR.
|
||||
|
||||
We use two simple functions to setup a simple web server in this section, and this simple server has already had ability for high concurrency. We will talk about how to use this feature in next two sections.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Web working principles](03.1.md)
|
||||
- Next section: [How Go works with web](03.3.md)
|
||||
85
en/eBook/03.3.md
Normal file
85
en/eBook/03.3.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 3.3 How Go works with web
|
||||
|
||||
We learned to use `net/http` package to build a simple web server in previous section, but all the working principles are the same as we talked in first section of this chapter.
|
||||
|
||||
## Some concepts in web working principles
|
||||
|
||||
Request: request data from users, including POST, GET, Cookie and URL.
|
||||
|
||||
Response: response data from server to clients.
|
||||
|
||||
Conn: connections between clients and servers.
|
||||
|
||||
Handler: logic of handle request and produce response.
|
||||
|
||||
## http package operating mechanism
|
||||
|
||||
The following picture shows that work flow of Go's web server.
|
||||
|
||||

|
||||
|
||||
Figure 3.9 http work flow
|
||||
|
||||
1. Create listening socket, listen to a port and wait for clients.
|
||||
2. Accept requests from clients.
|
||||
3. Handle requests, read HTTP header, if it uses POST method, also need to read data in message body and give them to handlers. Finally, socket returns response data to clients.
|
||||
|
||||
Once we know the answers of three following questions, it's easy to know how web works in Go.
|
||||
|
||||
- How to listen to a port?
|
||||
- How to accept client requests?
|
||||
- How to allocate handlers?
|
||||
|
||||
In the previous section we saw that Go uses `ListenAndServe` to handle these problems: initialize a server object, call `net.Listen("tcp", addr)` to setup a TCP listener and listen to specific address and port.
|
||||
|
||||
Let's take a look at `http` package's source code.
|
||||
|
||||
//Build version go1.1.2.
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
for {
|
||||
rw, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
How to accept client requests after listened to the port? In the source code, we can see that it calls `srv.Serve(net.Listener)` to handle client requests. In body of function there is a `for{}`, it accepts request, creates a new connection, and then starts a new goroutine, and passes request data to this goroutine: `go c.serve()`. This is how Go supports high concurrency, and every goroutine is independent.
|
||||
|
||||
Now, how to use specific functions to handle requests? `conn` parses request `c.ReadRequest()` at first, and get corresponding handler: `handler := c.server.Handler` which is the second argument we passed when we called `ListenAndServe`. Because we passed `nil`, so Go uses it's default handler `handler = DefaultServeMux`. So what is `DefaultServeMux` doing here? Well, this is the variable of router at this time, it calls handler functions for specific URLs. Did we setting this? Yes, we did. Remember in the first line we used `http.HandleFunc("/", sayhelloName)`. It's like you use this function to register the router rule for "/" path. When the URL is `/`, router calls function `sayhelloName`. DefaultServeMux calls ServerHTTP to get handler function for different path, and it calls `sayhelloName` in this case. Finally, server writes data and response to clients.
|
||||
|
||||
Detailed work flow:
|
||||
|
||||

|
||||
|
||||
Figure 3.10 Work flow of handling a HTTP request
|
||||
|
||||
I think you should know how Go runs web servers now.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Build a simple web server](03.2.md)
|
||||
- Next section: [Get into http package](03.4.md)
|
||||
135
en/eBook/03.4.md
Normal file
135
en/eBook/03.4.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 3.4 Get into http package
|
||||
|
||||
In previous sections, we learned the work flow of web, and talked about a little about `http` package. In this section, we are going to learn two core functions in `http` package: Conn, ServeMux.
|
||||
|
||||
## goroutine in Conn
|
||||
|
||||
Unlike normal HTTP servers, Go uses goroutine for every affair that created by Conn in order to achieve high concurrency and performance, so every affair is independent.
|
||||
|
||||
Go uses following code to wait for new connections from clients.
|
||||
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
|
||||
As you can see, it creates goroutine for every connection, and passes handler that is able to read data from request to the goroutine.
|
||||
|
||||
## Customized ServeMux
|
||||
|
||||
We used default router in previous section when talked about conn.server, the router passed request data to back-end handler.
|
||||
|
||||
The struct of default router:
|
||||
|
||||
type ServeMux struct {
|
||||
mu sync.RWMutex // because of concurrency, we have to use mutex here
|
||||
m map[string]muxEntry // router rules, every string mapping to a handler
|
||||
}
|
||||
|
||||
The struct of muxEntry:
|
||||
|
||||
type muxEntry struct {
|
||||
explicit bool // exact match or not
|
||||
h Handler
|
||||
}
|
||||
|
||||
The interface of Handler:
|
||||
|
||||
type Handler interface {
|
||||
ServeHTTP(ResponseWriter, *Request) // routing implementer
|
||||
}
|
||||
|
||||
`Handler` is a interface, but the function `sayhelloName` didn't implement this interface, why could we add it as handler? Because there is another type `HandlerFunc` in `http` package. We called `HandlerFunc` to define our `sayhelloName`, so `sayhelloName` implemented `Handler` at the same time. it's like we call `HandlerFunc(f)`, and function `f` is forced converted to type `HandlerFunc`.
|
||||
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
|
||||
How the router calls handlers after we set router rules?
|
||||
|
||||
The router calls `mux.handler.ServeHTTP(w, r)` when it receives requests. In other words, it calls `ServeHTTP` interface of handlers.
|
||||
|
||||
Now, let's see how `mux.handler` works.
|
||||
|
||||
func (mux *ServeMux) handler(r *Request) Handler {
|
||||
mux.mu.RLock()
|
||||
defer mux.mu.RUnlock()
|
||||
|
||||
// Host-specific pattern takes precedence over generic ones
|
||||
h := mux.match(r.Host + r.URL.Path)
|
||||
if h == nil {
|
||||
h = mux.match(r.URL.Path)
|
||||
}
|
||||
if h == nil {
|
||||
h = NotFoundHandler()
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
The router uses URL as a key to find corresponding handler that saved in map, and calls handler.ServeHTTP to execute functions to handle data.
|
||||
|
||||
You should understand the router work flow now, and Go actually supports customized routers. The second argument of `ListenAndServe` is for configuring customized router, it's a interface of `Handler`. Therefore, any router implements interface `Handler` that can be used.
|
||||
|
||||
The following example shows how to implement a simple router.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type MyMux struct {
|
||||
}
|
||||
|
||||
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
sayhelloName(w, r)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello myroute!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := &MyMux{}
|
||||
http.ListenAndServe(":9090", mux)
|
||||
}
|
||||
|
||||
## Go code execution flow
|
||||
|
||||
Let's take a look at the list of whole execution flow.
|
||||
|
||||
- Call `http.HandleFunc`
|
||||
1. Call HandleFunc of DefaultServeMux
|
||||
2. Call Handle of DefaultServeMux
|
||||
3. Add router rules to map[string]muxEntry of DefaultServeMux
|
||||
- Call `http.ListenAndServe(":9090", nil)`
|
||||
1. Instantiated Server
|
||||
2. Call ListenAndServe of Server
|
||||
3. Call net.Listen("tcp", addr) to listen to port.
|
||||
4. Start a loop, and accept requests in loop body.
|
||||
5. Instantiated a Conn and start a goroutine for every request: `go c.serve()`.
|
||||
6. Read request data: `w, err := c.readRequest()`.
|
||||
7. Check handler is empty or not, if it's empty then use DefaultServeMux.
|
||||
8. Call ServeHTTP of handler.
|
||||
9. Execute code in DefaultServeMux in this case.
|
||||
10. Choose handler by URL and execute code in that handler function: `mux.handler.ServeHTTP(w, r)`
|
||||
11. How to choose handler:
|
||||
A. Check router rules for this URL.
|
||||
B. Call ServeHTTP in that handler if there is one.
|
||||
C. Call ServeHTTP of NotFoundHandler otherwise.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [How Go works with web](03.3.md)
|
||||
- Next section: [Summary](03.5.md)
|
||||
11
en/eBook/03.5.md
Normal file
11
en/eBook/03.5.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 3.5 Summary
|
||||
|
||||
In this chapter, we introduced HTTP, DNS resolution flow and how to build a simple web server. Then we talked about how Go implements web server for us by looking at source code of package `net/http`.
|
||||
|
||||
I hope you know much more about web development, and you should see that it's quite easy and flexible to build a web application in Go.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Get into http package](03.4.md)
|
||||
- Next chapter: [User form](04.0.md)
|
||||
23
en/eBook/04.0.md
Normal file
23
en/eBook/04.0.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 4 User form
|
||||
|
||||
User form is the tool that is commonly used when we develop web applications, it provides ability to communicate between clients and servers. You must know form very much if you are web developers; if you are C/C++ programmers, you may want to ask: what is the user form?
|
||||
|
||||
Form is the area that contains form elements. Users can input information in these elements, like text box, drop down list, radio buttons, check box, etc. We use form tag `<form>` to define form.
|
||||
|
||||
<form>
|
||||
...
|
||||
input elements
|
||||
...
|
||||
</form>
|
||||
|
||||
Go has already had many convenient functions to deal with use form, like you can easily get form data in HTTP requests, and there are easy to integrate to your own web applications. In section 4.1, we are going to talk about how to handle form data in Go, and because you cannot believe any data from clients, you have to verify data before use them. After that, we will show some examples about verifying form data in section 4.2.
|
||||
|
||||
We say that HTTP is stateless, how can we identify that these forms are from same user? And how to make sure that one form can only be submitted once? We have more detailed explanation about Cookie (Cookie is the information that can be saved in the client side, and put in the request header when the request is sent to the server) in both sections 4.3 and 4.4.
|
||||
|
||||
Another big feature of form is uploading files. In section 4.5, you will learn how to use this feature and control file size before it starts uploading in Go.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous chapter: [Chapter 3 Summary](03.5.md)
|
||||
- Next section: [Process form inputs](04.1.md)
|
||||
107
en/eBook/04.1.md
Normal file
107
en/eBook/04.1.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 4.1 Process form inputs
|
||||
|
||||
Before we start talking, let's take a look a simple example of use form, save it as `login.gtpl` in your project folder.
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
Username:<input type="text" name="username">
|
||||
Password:<input type="password" name="password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
This form will submit to `/login` in server. After user clicked log in button, the data will be sent to `login` handler in server router. Then we need to know it uses POST method or GET.
|
||||
|
||||
It's easy to know through `http` package, let's see how to handle form data in log in page.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sayhelloName(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm() //Parse url parameters passed, then parse the response packet for the POST body (request body)
|
||||
// attention: If you do not call ParseForm method, the following data can not be obtained form
|
||||
fmt.Println(r.Form) // print information on server side.
|
||||
fmt.Println("path", r.URL.Path)
|
||||
fmt.Println("scheme", r.URL.Scheme)
|
||||
fmt.Println(r.Form["url_long"])
|
||||
for k, v := range r.Form {
|
||||
fmt.Println("key:", k)
|
||||
fmt.Println("val:", strings.Join(v, ""))
|
||||
}
|
||||
fmt.Fprintf(w, "Hello astaxie!") // write data to response
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //get request method
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, nil)
|
||||
} else {
|
||||
r.ParseForm()
|
||||
// logic part of log in
|
||||
fmt.Println("username:", r.Form["username"])
|
||||
fmt.Println("password:", r.Form["password"])
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", sayhelloName) // setting router rule
|
||||
http.HandleFunc("/login", login)
|
||||
err := http.ListenAndServe(":9090", nil) // setting listening port
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Now we know use `r.Method` to get request method, and it returns a string like "GET", "POST", "PUT", etc.
|
||||
|
||||
In function `login`, we use `r.Method` to check if it's a log in page or log in process logic, which means you just open this page, or you are trying to log in. Serve shows page only when it uses GET method, process log in logic when it uses POST method.
|
||||
|
||||
You should see following interface after you opened `http://127.0.0.1:9090/login` in your browser.
|
||||
|
||||

|
||||
|
||||
Figure 4.1 User log in interface
|
||||
|
||||
Server will not print anything after we typed user name and password because handler doesn't parse form until we call `r.ParseForm()`. Let's add `r.ParseForm()` before `fmt.Println("username:", r.Form["username"])`, compile and test again. You should see information is printed in server side now.
|
||||
|
||||
`r.Form` contains all request arguments, like query-string in URL, data in POST and PUT. If the data have conflicts like have same name, it will save into a slice with multiple values. In Go documentation, it says Go will save data of GET and POST in different places.
|
||||
|
||||
Try to change value of action in form `http://127.0.0.1:9090/login` to `http://127.0.0.1:9090/login?username=astaxie` in file `login.gtpl`, test again, and you will see the slice is printed in server side.
|
||||
|
||||

|
||||
|
||||
Figure 4.2 Server prints request data
|
||||
|
||||
The type of `request.Form` is `url.Value`, it saves data with format `key=value`.
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", "Ava")
|
||||
v.Add("friend", "Jess")
|
||||
v.Add("friend", "Sarah")
|
||||
v.Add("friend", "Zoe")
|
||||
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
|
||||
fmt.Println(v.Get("name"))
|
||||
fmt.Println(v.Get("friend"))
|
||||
fmt.Println(v["friend"])
|
||||
|
||||
**Tips** Request has ability to access form data by method `FormValue()`. For example, you can change `r.Form["username"]` to `r.FormValue("username")`, and it calls `r.ParseForm` automatically. Notice that it returns the first value if there are arguments with same name, and it returns empty string if there is no such argument.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [User form](04.0.md)
|
||||
- Next section: [Verification of inputs](04.2.md)
|
||||
141
en/eBook/04.2.md
Normal file
141
en/eBook/04.2.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 4.2 Verification of inputs
|
||||
|
||||
The most important principle in web development is that you cannot trust anything from user form, you have to verify all data before use them. You may know many websites are invaded by this problem which is simple but crucial.
|
||||
|
||||
There are two ways to verify form data that commonly used, the one is JavaScript verification in front-end, and another one is server verification in back-end. In this section, we are going to talk about the server verification in web development.
|
||||
|
||||
## Required fields
|
||||
|
||||
Sometimes you ask users to input some fields but they don't, for example we need user name in previous section. You can use function `len` to get length of field to make sure users input this information.
|
||||
|
||||
if len(r.Form["username"][0])==0{
|
||||
// code for empty field
|
||||
}
|
||||
|
||||
`r.Form` uses different treatments of different types of form elements when they are blanks. For empty text box, text area and file upload, it returns empty string; for radio button and check box, it doesn't even create corresponding items, and you will get errors if you try to access it. Therefore, we'd better use `r.Form.Get()` to get filed values because it always returns empty if the value does not exist. On the other hand, `r.Form.Get()` can only get one field value every time, so you need to use `r.Form` to get values in map.
|
||||
|
||||
## Numbers
|
||||
|
||||
Sometimes you only need numbers for the field value. For example, you need age of users, like 50 or 10, instead of "old enough" or "young man". If we need positive numbers, we can convert to `int` type first and process them.
|
||||
|
||||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||||
if err!=nil{
|
||||
// error occurs when convert to number, it may not a number
|
||||
}
|
||||
|
||||
// check range of number
|
||||
if getint >100 {
|
||||
// too big
|
||||
}
|
||||
|
||||
Another way to do this is using regular expression.
|
||||
|
||||
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
For high performance purpose, regular expression is not an efficient way, but simple regular expression is fast enough. If you know regular expression before, you should it's a very convenient way to verify data. Notice that Go uses [RE2](http://code.google.com/p/re2/wiki/Syntax), all UTF-8 characters are supported.
|
||||
|
||||
## Chinese
|
||||
|
||||
Sometimes we need users to input their Chinese name, we have to verify they use all Chinese rather than random characters. For Chinese verification, regular expression is the only way.
|
||||
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
## English letters
|
||||
|
||||
Sometimes we need users to input English letters. For example, we need someone's English name, like astaxie instead of asta谢. We can easily use regular expression to do verification.
|
||||
|
||||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||||
return false
|
||||
}
|
||||
|
||||
## E-mail address
|
||||
|
||||
If you want to know if users input valid E-mail address, you can use following regular expression:
|
||||
|
||||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||||
fmt.Println("no")
|
||||
}else{
|
||||
fmt.Println("yes")
|
||||
}
|
||||
|
||||
## Drop down list
|
||||
|
||||
When we need item in our drop down list, but we get some values that are made by hackers, how can we prevent it?
|
||||
|
||||
Suppose we have following `<select>`:
|
||||
|
||||
<select name="fruit">
|
||||
<option value="apple">apple</option>
|
||||
<option value="pear">pear</option>
|
||||
<option value="banane">banane</option>
|
||||
</select>
|
||||
|
||||
Then, we use following way to verify:
|
||||
|
||||
slice:=[]string{"apple","pear","banane"}
|
||||
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("fruit") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
All functions I showed above are in my open source project for operating slice and map: [https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
|
||||
|
||||
## Radio buttons
|
||||
|
||||
If we want to know the user is male or female, we may use a radio button, return 1 for male and 2 for female. However, there is a little boy is reading book about HTTP, and send to you 3, will your program have exception? So we need to use same way for drop down list to make sure all values are expected.
|
||||
|
||||
<input type="radio" name="gender" value="1">Male
|
||||
<input type="radio" name="gender" value="2">Female
|
||||
|
||||
And we use following code to do verification:
|
||||
|
||||
slice:=[]int{1,2}
|
||||
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("gender") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
## Check boxes
|
||||
|
||||
Suppose there are some check boxes for users' interests, and you don't want extra values as well.
|
||||
|
||||
<input type="checkbox" name="interest" value="football">Football
|
||||
<input type="checkbox" name="interest" value="basketball">Basketball
|
||||
<input type="checkbox" name="interest" value="tennis">Tennis
|
||||
|
||||
Here is a little bit different in verification between radio buttons and check boxes because we get a slice from check boxes.
|
||||
|
||||
slice:=[]string{"football","basketball","tennis"}
|
||||
a:=Slice_diff(r.Form["interest"],slice)
|
||||
if a == nil{
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
## Date and time
|
||||
|
||||
Suppose you want to make users input valid date or time. Go has package `time` to convert year, month, day to corresponding time, then it's easy to check it.
|
||||
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Printf("Go launched at %s\n", t.Local())
|
||||
|
||||
After you had time, you can use package `time` for more operations depend on your purposes.
|
||||
|
||||
We talked about some common form data verification in server side, I hope you understand more about data verification in Go, especially how to use regular expression.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Process form inputs](04.1.md)
|
||||
- Next section: [Cross site scripting](04.3.md)
|
||||
68
en/eBook/04.3.md
Normal file
68
en/eBook/04.3.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 4.3 Cross site scripting
|
||||
|
||||
Today's websites have much more dynamic content in order to improve user experience, which means we can provide dynamic information depends on every individual's behavior. However, there is a thing called "Cross site scripting" (known as "XSS") always attacking dynamic websites, and static websites are completely fine at this time.
|
||||
|
||||
Attackers often inject malicious scripts like JavaScript, VBScript, ActiveX or Flash into those websites that have loopholes. Once they have successful injection, your user information will be stolen and your website will full of spam, also they can change user settings to whatever they want.
|
||||
|
||||
If you want to prevent this kind of attack, you'd better combine two following approaches:
|
||||
|
||||
- Verification all data from users, which we talked about previous section.
|
||||
- Give special handling for data that will be responded to clients, in order to prevent any injected script runs on browsers.
|
||||
|
||||
So how can we do these two jobs in Go? Fortunately, package `html/template` has some useful functions to escape data as follows:
|
||||
|
||||
- `func HTMLEscape(w io.Writer, b []byte)` escapes b to w.
|
||||
- `func HTMLEscapeString(s string) string` returns string after escaped from s.
|
||||
- `func HTMLEscaper(args ...interface{}) string` returns string after escaped from multiple arguments.
|
||||
|
||||
Let's change the example in section 4.1:
|
||||
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // print at server side
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // responded to clients
|
||||
|
||||
If we try to input user name as `<script>alert()</script>`, we will see following content in the browser:
|
||||
|
||||

|
||||
|
||||
Figure 4.3 JavaScript after escaped
|
||||
|
||||
Functions in package `html/template` help you escape all HTML tags, what if you just want to print `<script>alert()</script>` to browsers? You should use `text/template` instead.
|
||||
|
||||
import "text/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
|
||||
Output:
|
||||
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
|
||||
Or you can use type `template.HTML`:
|
||||
Variable content will not be escaped if it's type is `template.HTML`.
|
||||
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
|
||||
|
||||
Output:
|
||||
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
|
||||
One more example of escape
|
||||
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
|
||||
Output:
|
||||
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Verification of inputs](04.2.md)
|
||||
- Next section: [Duplicate submissions](04.4.md)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user