http 編程
Go 原生支持http:
import "net/http"
Go 的http服務(wù)性能和nginx比較接近:
就是說(shuō)用Go寫(xiě)的Web程序上線,程序前面不需要再部署nginx的Web服務(wù)器,這里省掉的是Web服務(wù)器。如果服務(wù)器上部署了多個(gè)Web應(yīng)用,還是需要反向代理的,一般這也是nginx或apache。
幾行代碼就可以實(shí)現(xiàn)一個(gè)web服務(wù):
package main
import (
"fmt"
"net/http"
)
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Println(*r)
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", Hello)
err := http.ListenAndServe("0.0.0.0:8000", nil)
if err != nil {
fmt.Println("http Listen failed")
}
}
http client
http 常見(jiàn)的請(qǐng)求方法:
- Get請(qǐng)求
- Post請(qǐng)求
- Put請(qǐng)求
- Delete請(qǐng)求
- Head請(qǐng)求
Get 請(qǐng)求
使用Get請(qǐng)求網(wǎng)站的示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
res, err := http.Get("http://edu.51cto.com")
if err != nil {
fmt.Println("http get ERRPR:", err)
return
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("get data ERROR:", err)
return
}
fmt.Println(string(data))
}
Head請(qǐng)求
Head請(qǐng)求只返回響應(yīng)頭。如果只想要獲取一些狀態(tài)信息的話,可以用Head請(qǐng)求。這樣避免返回響應(yīng)體,響應(yīng)體的數(shù)據(jù)是比較多的,適合做監(jiān)控。Head請(qǐng)求的示例:
package main
import (
"fmt"
"net/http"
)
var urls = []string{
"http://×××w.baidu.com",
"http://×××w.google.com",
"http://×××w.sina.com.cn",
"http://×××w.163.com",
}
func main() {
for _, v := range urls {
resp, err := http.Head(v)
if err != nil {
fmt.Println("Head request ERROR:", err)
continue
}
fmt.Println(resp.Status)
}
}
http 常見(jiàn)狀態(tài)碼
http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302 跳轉(zhuǎn)
http.StatusBadRequest = 400 非法請(qǐng)求
http.StatusUnanthorized = 401 沒(méi)有權(quán)限
http.StatusForbidden = 403 禁止訪問(wèn)
http.Status.NotFound = 404 頁(yè)面不存在
http.StatusInternalServerError = 500 內(nèi)部錯(cuò)誤
處理form表單
package main
import (
"fmt"
"io"
"net/http"
)
const form = `
html>
body>
form action="#" method="post" name="bar">
input type="text" name="in" />
input type="text" name="in" />
input type="submit" value="Submit" />
/form>
/body>
/html>`
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("content-Type", "text/html")
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
request.ParseForm()
io.WriteString(w, request.Form["in"][0]) // 注意上面的2個(gè)input的name是一樣的
io.WriteString(w, request.Form["in"][1]) // 所以這是一個(gè)數(shù)組
io.WriteString(w, "/br>")
io.WriteString(w, request.FormValue("in")) // 一般去一個(gè)值,就用這個(gè)方法
}
}
func main() {
http.HandleFunc("/form", FormServer)
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println("監(jiān)聽(tīng)端口ERROR:", err)
}
}
panic 處理
如果處理函數(shù)里有panic,會(huì)導(dǎo)致整個(gè)程序崩潰,所以要 defer revoer()
來(lái)處理 panic。在處理函數(shù)開(kāi)頭defer一個(gè)匿名函數(shù):
func FormServer(w http.ResponseWriter, request *http.Request) {
// 增加一個(gè)defer來(lái)處理panic
defer func() {
if x := recover(); x != nil {
log.Println(request.RemoteAddr, "捕獲到異常:", x)
}
}()
// 原本的處理函數(shù)的內(nèi)容
w.Header().Set("content-Type", "text/html")
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
request.ParseForm()
io.WriteString(w, request.FormValue("in")) // 一般去一個(gè)值,就用這個(gè)方法
}
// 搞個(gè)panic出來(lái)
zero := 0
tmp := 1 / zero
io.WriteString(w, string(tmp))
}
優(yōu)化統(tǒng)一處理
按照上面的做法,要在每個(gè)處理函數(shù)的開(kāi)頭都加上panic的處理。由于每個(gè)處理函數(shù)的panic處理方法都一樣,所以可以寫(xiě)一個(gè)自定義的處理函數(shù):
// 自定義的panic處理的函數(shù)
func logPanics(handle http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if x := recover(); x != nil {
log.Println(request.RemoteAddr, "捕獲到異常:", x)
}
}()
// 上面先處理panic,再接著下面調(diào)用業(yè)務(wù)邏輯
handle(writer, request)
}
}
func main() {
// http.HandleFunc("/form", FormServer) // 修改調(diào)用處理函數(shù)的方法
http.HandleFunc("/form", logPanics(FormServer)) // 把處理函數(shù)傳給自己寫(xiě)的封裝了panic處理的函數(shù)里
if err := http.ListenAndServe(":8000", nil); err != nil {
fmt.Println("監(jiān)聽(tīng)端口ERROR:", err)
}
}
原本直接調(diào)用處理函數(shù)。現(xiàn)在調(diào)用自定義的函數(shù),把處理函數(shù)傳進(jìn)去。在自定義的函數(shù)里先加載defer,然后再調(diào)用執(zhí)行原本的處理函數(shù)。邏輯很簡(jiǎn)單,就是把處理函數(shù)作為參數(shù)傳給自定義的函數(shù),在自定義的函數(shù)里再調(diào)用處理函數(shù)。在自定義的函數(shù)里寫(xiě)上defer,這樣就相當(dāng)于所有的處理函數(shù)都有defer了。
模板
使用模板需要用到 "text/template" 包。然后調(diào)用模板的t.Execute()方法輸出。
替換
先準(zhǔn)備一個(gè)簡(jiǎn)單的模板:
p>Hello {{.Name}}/p>
p>Age: {{.Age}}/p>
然后在Go里使用模板:
package main
import (
"fmt"
"os"
"text/template"
)
type Person struct {
Name string
Age int
}
func main() {
t, err := template.ParseFiles("index.html")
if err != nil {
fmt.Println("模板解析異常:", err)
return
}
p := Person{"Bob", 32}
if err := t.Execute(os.Stdout, p); err != nil {
fmt.Println("模板加載數(shù)據(jù)異常:", err)
}
}
/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day10\http\use_template> go run main.go
p>Hello Bob/p>
p>Age: 32/p>
PS H:\Go\src\go_dev\day10\http\use_template>
*/
如果直接用 {{.}} 不加字段名的話,就是輸出結(jié)構(gòu)體打印的效果。
輸出到瀏覽器里
要輸出到瀏覽器里,只需要在 t.Execute(os.Stdout, p)
里,把原本輸出到終端換成輸出到處理函數(shù)的 w http.ResponseWriter 類型,就好了。
html模板的內(nèi)容不變,下面是go的代碼:
package main
import (
"fmt"
"net/http"
"text/template"
)
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
type Person struct {
Name string
Age int
}
func Index(w http.ResponseWriter, r *http.Request) {
p := Person{"Cara", 18}
t, err := template.ParseFiles("index.html")
if err != nil {
fmt.Println("加載模板ERROR:", err)
return
}
t.Execute(w, p)
}
func main() {
http.HandleFunc("/", Hello)
http.HandleFunc("/index", Index)
err := http.ListenAndServe("0.0.0.0:8000", nil)
if err != nil {
fmt.Println("http Listen failed")
}
}
判斷
用法示例:
body>
{{if gt .Age 18}}
p>已成年/p>
{{else}}
p>未成年/p>
{{end}}
/body>
更多判斷邏輯:
not 非
{{if not .condition}}
{{end}}
and 與
{{if and .condition1 .condition2}}
{{end}}
or 或
{{if or .condition1 .condition2}}
{{end}}
eq 等于
{{if eq .var1 .var2}}
{{end}}
ne 不等于
{{if ne .var1 .var2}}
{{end}}
lt 小于
{{if lt .var1 .var2}}
{{end}}
le 小于等于
{{if le .var1 .var2}}
{{end}}
gt 大于
{{if gt .var1 .var2}}
{{end}}
ge 大于等于
{{if ge .var1 .var2}}
{{end}}
with 封裝
with語(yǔ)句就是創(chuàng)建一個(gè)封閉的作用域,在其范圍內(nèi),{{.}}代表with的變量,而與外面的{{.}}無(wú)關(guān),只與with的參數(shù)有關(guān):
body>
{{with .Name}}
p>{{.}}/p>
{{end}}
/body>
上面這樣包在 {{with .Var}} 里,with 里的 {{.}} 代表的就是 Var 這個(gè)變量。
with 可以封裝常數(shù):
{{ with "world"}}
Now the dot is set to {{ . }}
{{ end }}
循環(huán)(遍歷)
golang的template支持range循環(huán)來(lái)遍歷map、slice內(nèi)的內(nèi)容,在range循環(huán)內(nèi),還可以使用$設(shè)置循環(huán)變量,我們可以通過(guò) $i $v 來(lái)訪問(wèn)遍歷的值。語(yǔ)法為:
{{range $i, $v := .slice}}
li>key: {{ $key }}, value: {{ $value }}/li>
{{end}}
這是另外一種遍歷方式,這種方式無(wú)法訪問(wèn)到index或者key的值,需要通過(guò)點(diǎn)來(lái)訪問(wèn)對(duì)應(yīng)的value:
{{range .slice}}
{{.field}}
{{end}}
在循環(huán)內(nèi),點(diǎn)是代表遍歷的值。原本使用點(diǎn)來(lái)訪問(wèn)的變量,那么在循環(huán)內(nèi)部就要用 $. 來(lái)訪問(wèn)。下面的例子表示循環(huán)內(nèi)和循環(huán)外 ArticleConten 這個(gè)變量訪問(wèn)的方式:
{{.ArticleContent}}
{{range .slice}}
{{$.ArticleContent}}
{{end}}
定義變量
模板的參數(shù)可以是go中的基本數(shù)據(jù)類型,如字串,數(shù)字,布爾值,數(shù)組切片或者一個(gè)結(jié)構(gòu)體。在模板中設(shè)置變量可以使用 $variable := value。我們?cè)趓ange迭代的過(guò)程使用了設(shè)置變量的方式。
{{$article := "hello"}}
{{$name := .Name}}
mysql 使用
這里只簡(jiǎn)單講了數(shù)據(jù)的增刪改查,所以測(cè)試代碼前,需要先把數(shù)據(jù)庫(kù)準(zhǔn)備好。
先創(chuàng)建一個(gè)數(shù)據(jù)庫(kù),指定了編碼,這樣應(yīng)該可以支持中文:
CREATE DATABASE 庫(kù)名 CHARSET "utf8";
然后建2張表:
CREATE TABLE person (
user_id int primary key auto_increment,
username varchar(260),
gender varchar(260),
email varchar(260)
);
CREATE TABLE place (
country varchar(200),
city varchar(200),
telcode int
);
導(dǎo)入數(shù)據(jù)庫(kù)驅(qū)動(dòng)
sql 包提供了通用的SQL(或類SQL)數(shù)據(jù)庫(kù)接口。
sql 包必須與數(shù)據(jù)庫(kù)驅(qū)動(dòng)結(jié)合使用。
驅(qū)動(dòng)包需要安裝:
go get -u github.com/go-sql-driver/mysql
使用前,先要導(dǎo)入mysql的包:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
上面導(dǎo)入了2個(gè)包。第一個(gè)是sql包,就是我們調(diào)用操作數(shù)據(jù)庫(kù)用的。
第二個(gè)是驅(qū)動(dòng)包,這里前面加了占位符,所以這個(gè)包只是引入,但是不使用它。并且如果要操作別的數(shù)據(jù)庫(kù)的話,只需要修改驅(qū)動(dòng)包就行了。
連接數(shù)據(jù)庫(kù)
構(gòu)建連接, 格式是:”用戶名:密碼@tcp(IP:端口)/數(shù)據(jù)庫(kù)?charset=utf8” :
package main
import (
"fmt"
"time"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
var DB *sql.DB
func init() {
database, err := sql.Open("mysql", "admin:admin123@tcp(192.168.3.103:3306)/Golang_week10")
if err != nil {
fmt.Println("連接數(shù)據(jù)庫(kù)失敗:", err)
return
}
DB = database
}
func main() {
fmt.Println(DB)
DB.SetMaxIdleConns(16) //設(shè)置閑置連接數(shù)
DB.SetMaxOpenConns(100) //設(shè)置最大連接數(shù)
DB.SetConnMaxLifetime(100*time.Second) //最大連接周期,超過(guò)時(shí)間的連接就close
fmt.Println(DB)
}
插入數(shù)據(jù)
下面是插入數(shù)據(jù),并且再獲取id的示例:
// 數(shù)據(jù)庫(kù)連接的init函數(shù)就省略了
func insert() {
r, err := DB.Exec("insert into person(username,gender,email) values(?,?,?)", "Barry", "Male", "Barry@go.net")
if err != nil {
fmt.Println("插入數(shù)據(jù)ERROR:", err)
return
}
fmt.Println(r)
id, err := r.LastInsertId()
if err != nil {
fmt.Println("獲取id ERROR:", err)
return
}
fmt.Println(id)
}
func main() {
insert()
}
上面的 values(?,?,?) 里的問(wèn)號(hào),是占位符,具體的值可以寫(xiě)在后面的參數(shù)里。當(dāng)然如果不用占位符,直接就傳1個(gè)字符串作為參數(shù)也是可以的。
查詢
查詢單個(gè)字段:
func query() {
row := DB.QueryRow("select username from person where user_id=?", 1)
var name string // 創(chuàng)建變量用于存放查詢到的數(shù)據(jù)
if err := row.Scan(name); err != nil {
fmt.Println("Scan Failed:", err)
return
}
fmt.Println(name)
}
func main() {
query()
}
也可以一次查詢多個(gè)字段或所有字段,查詢之前按照表的類型創(chuàng)建結(jié)構(gòu)體,用查詢到的數(shù)據(jù)為結(jié)構(gòu)體賦值:
type Person struct {
ID int `db:"user_id"`
Username sql.NullString `db:"username"`
Gender sql.NullString `db:"gender"`
Email sql.NullString `db:"email"`
}
func query() {
row := DB.QueryRow("select * from person where user_id=?", 6)
var person = new(Person)
// row.scan中的字段必須是按照數(shù)據(jù)庫(kù)存入字段的順序,否則報(bào)錯(cuò)
if err := row.Scan(person.ID, person.Username, person.Gender, person.Email); err != nil {
fmt.Println("Scan Failed:", err)
return
}
fmt.Println(person)
}
func main() {
query()
}
數(shù)據(jù)模型,就是上面定義的結(jié)構(gòu)體。這里的類型可以是Go的標(biāo)準(zhǔn)數(shù)據(jù)類型。但是如果數(shù)據(jù)庫(kù)的字段允許為空,并且該字段的值也為空,那么查詢后該字段會(huì)返回nil。如果是string類型,則無(wú)法接收nil,但sql.NullString則可以接收nil值。
另外,結(jié)構(gòu)體里的tag標(biāo)簽在這里沒(méi)有意義。不過(guò)上面的tag標(biāo)注了該字段在數(shù)據(jù)庫(kù)里對(duì)應(yīng)的字段名,可能在別處會(huì)有用。
查詢多行
func query() {
rows, err := DB.Query("select * from person where user_id > ?", 1)
defer func() {
if rows != nil {
rows.Close()
}
}()
if err != nil {
fmt.Println("Query 查詢 ERROR:", err)
return
}
var person = new(Person)
for rows.Next() {
if err = rows.Scan(person.ID, person.Username, person.Gender, person.Email); err != nil {
fmt.Println("Scan Failed:", err)
return
}
fmt.Println(person)
}
}
func main() {
query()
}
查詢用起來(lái)還是不太方法,不過(guò)還可以選擇其他第三方庫(kù),那里會(huì)有一些很好的擴(kuò)展。后面會(huì)舉例。
其他操作
由于基本都是用SQL的命令進(jìn)行操作,所以其他操作就不一個(gè)一個(gè)舉例了
update 更新數(shù)據(jù)
result, err := DB.Exec("UPDATE person set email=? where username=?", "Cara", Cara@catco.org)
delete 刪除數(shù)據(jù)
result,err := DB.Exec("DELETE FROM person where id=?",1)
注意:更新數(shù)據(jù)不返回LastInsertID,所以result.LastInsertID一直為0。刪除數(shù)據(jù)可以拿到LastInsertID,用法和插入數(shù)據(jù)里一樣。
第三方庫(kù) sqlx
sqlx是一個(gè)go語(yǔ)言包,在內(nèi)置database/sql包之上增加了很多擴(kuò)展,簡(jiǎn)化數(shù)據(jù)庫(kù)操作代碼的書(shū)寫(xiě)。
由于database/sql接口是sqlx的子集,所有database/sql的用法,在sqlx中一樣可以用。不過(guò)sqlx還有更多擴(kuò)展,用起來(lái)更方便。
安裝:
go get github.com/jmoiron/sqlx
查詢 Select() 方法
Select是一個(gè)非常省時(shí)的擴(kuò)展。它們把query和非常靈活的scan語(yǔ)法結(jié)合起來(lái)。Select用來(lái)獲取結(jié)果切片:
// 這里的tag標(biāo)簽就有意義了,下面的Select()方法應(yīng)該就是根據(jù)tag標(biāo)簽對(duì)號(hào)入座的
type Person struct {
ID int `db:"user_id"`
Username sql.NullString `db:"username"`
Gender sql.NullString `db:"gender"`
Email sql.NullString `db:"email"`
}
func select() {
var persons []Person // 這里創(chuàng)建的是存放結(jié)構(gòu)體的切片
if err := DB.Select(person, "select * from person where userid > ?", 1); err != nil {
fmt.Println("Select ERROR:", err)
return
}
fmt.Println(person)
}
Select可以提高編碼效率,還有更多擴(kuò)展。sqlx 號(hào)稱 golang 數(shù)據(jù)庫(kù)開(kāi)發(fā)神器,這里就提一下,等到真正用的時(shí)候再去深入學(xué)習(xí)了。
您可能感興趣的文章:- Go語(yǔ)言操作mysql數(shù)據(jù)庫(kù)簡(jiǎn)單例子
- Go語(yǔ)言使用MySql的方法
- Golang中如何對(duì)MySQL進(jìn)行操作詳解
- golang gorm 操作mysql及gorm基本用法
- 在golang中操作mysql數(shù)據(jù)庫(kù)的實(shí)現(xiàn)代碼
- golang實(shí)現(xiàn)mysql數(shù)據(jù)庫(kù)備份的操作方法
- Go語(yǔ)言集成mysql驅(qū)動(dòng)、調(diào)用數(shù)據(jù)庫(kù)、查詢數(shù)據(jù)操作示例