Go筆記-Web基礎

Web基礎

搭建一個簡單的Web服務器

import (
	"fmt"
	"log"
	"net/http"
	"strings"
)

func Main() {
	http.HandleFunc("/", sayHello)           // 設置訪問的路由
	err := http.ListenAndServe(":9090", nil) //設置監聽的端口
	if err != nil {
		log.Fatal("listenAndServe Err:", err)
	}
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	r.ParseForm() //解析參數,默認是不會解析的
	fmt.Println(r.Form)
	fmt.Println("url_long", r.Form["url_long"])

	fmt.Println("path:", r.URL.Path)
	fmt.Println("scheme:", r.URL.Scheme)

	for k, v := range r.Form {
		s := "key:" + k + ", "
		s += "value:" + strings.Join(v, ",")
		fmt.Println(s)
	}

	fmt.Fprintf(w, "hello")
}

自定義路由

import (
	"fmt"
	"net/http"
)

type MyMux struct {
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		sayHello(w, r)
		return
	}
	http.NotFound(w, r)
	return
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello")
}

func Main() {
	mux := &MyMux{}
	http.ListenAndServe(":9090", mux)
}

靜態文件

註冊:css

http.Handle("/tpl/css/", http.FileServer(http.Dir("./")))

請求時處理:html

http.ServeFile()

Or:web

fileHandler := http.FileServer(http.Dir("static/websocket"))
http.Handle("/wsfile/", http.StripPrefix("/wsfile/", fileHandler))

訪問時使用 http://localhost:9090/wsfile/ws.html正則表達式

Or:數組

func StaticServer(addr string, paths map[string]string) {
	servers := make(map[string]http.Handler, len(paths))
	for pattern, path := range paths {
		servers[pattern] = http.FileServer(http.Dir(path))
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		urlPath := r.URL.Path
		for pattern, handler := range servers {
			if strings.HasPrefix(urlPath, pattern) {
				http.StripPrefix(pattern, handler).ServeHTTP(w, r)
				return
			}
		}
		http.NotFound(w, r)
	})
	log.Fatal(http.ListenAndServe(addr, nil))
}

執行流程

經過對http包的分析以後,如今讓咱們來梳理一下整個的代碼執行過程。瀏覽器

首先調用Http.HandleFunc服務器

按順序作了幾件事:websocket

  1. 調用了DefaultServeMux的HandleFunc
  2. 調用了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增長對應的handler和路由規則

其次調用http.ListenAndServe(":9090", nil)cookie

按順序作了幾件事情:session

  1. 實例化Server
  2. 調用Server的ListenAndServe()
  3. 調用net.Listen("tcp", addr)監聽端口
  4. 啓動一個for循環,在循環體中Accept請求
  5. 對每一個請求實例化一個Conn,而且開啓一個goroutine爲這個請求進行服務go c.serve()
  6. 讀取每一個請求的內容w, err := c.readRequest()
  7. 判斷handler是否爲空,若是沒有設置handler(這個例子就沒有設置handler),handler就設置爲DefaultServeMux
  8. 調用handler的ServeHttp
  9. 在這個例子中,下面就進入到DefaultServeMux.ServeHttp
  10. 根據request選擇handler,而且進入到這個handler的ServeHTTP mux.handler(r).ServeHTTP(w, r)
  11. 選擇handler:
  • A 判斷是否有路由能知足這個request(循環遍歷ServerMux的muxEntry)
  • B 若是有路由知足,調用這個路由handler的ServeHttp
    • C 若是沒有路由知足,調用NotFoundHandler的ServeHttp

重定向

http.Redirect(w, r, "/", 302)

302 可換成 http.StatusFound

not found

http.NotFount(w, r)

代理服務

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"
)

func main() {
	server := http.Server{
		Addr:           ":8080",
		Handler:        NewReverseProxyHandler(),
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   120 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	log.Fatal(server.ListenAndServe())
}

type ReverseProxyHandler struct {
	innerProxy *httputil.ReverseProxy
}

func NewReverseProxyHandler() *ReverseProxyHandler {
	innerProxy := &httputil.ReverseProxy{
		Director: redirectRequest,
	}
	return &ReverseProxyHandler{
		innerProxy: innerProxy,
	}
}

func (handler *ReverseProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if !strings.HasPrefix(path, "/index") {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("bad request"))
		return
	}
	fmt.Println(path)
	handler.innerProxy.ServeHTTP(w, r)
}

func redirectRequest(r *http.Request) {
	u := r.URL
	u.Host = "127.0.0.1:9099"
	u.Scheme = "http"
}

表單

處理表單的輸入

在src/web/form下新建一個login.gtpl

<html>
<head>
<title></title>
</head>
<body>
<form action="/login" method="post">
    用戶名:<input type="text" name="username">
    密碼:<input type="password" name="password">
    <input type="submit" value="登錄">
</form>
</body>
</html>

編寫處理邏輯

package form

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
)

func login(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method)
	if r.Method == "GET" {
		t, err := template.ParseFiles("form/login.gtpl")
		if err != nil {
			fmt.Println("parseFiles:", err)
			log.Fatal("parseFiles:", err)
			return
		}
		fmt.Println(t.Name())
		t.Execute(w, nil)
	} else {
		r.ParseForm()
		fmt.Println("username:", r.Form["username"])
		fmt.Println("password", r.Form["password"])
	}
}

func Main() {
	http.HandleFunc("/login", login)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("listenAndServe:", err)
	}
}

在瀏覽器訪問http://localhost:9090/login

驗證表單

必填字段

r.Form[「name」]獲取的是數組

r.Form.Get(「name」)若是不存在則返回空字符串,但只能返回第一個name的值

數字

getint, err := strconv.Atoi(r.Form.Get("age"))
	if err != nil {
		//數字轉化出錯了,那麼可能就不是數字
	}

或者使用正則表達式:

if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
    return false
}

中文

if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
    return false
}

英文

if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
    return false
}

電子郵件

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")
}

手機號碼

if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
    return false
}

模板

獲取模板

gtpl模板文件

t, err := template.ParseFiles("form/login.gtpl") // 相對於運行的exe程序
	if err != nil {
		log.Fatal("parseFiles:", err)
		return
	}
	t.Execute(w, nil)

轉義

func HTMLEscape(w io.Writer, b []byte) //把b進行轉義以後寫到w
func HTMLEscapeString(s string) string //轉義s以後返回結果字符串
func HTMLEscaper(args ...interface{}) string //支持多個參數一塊兒轉義,返回結果字符串

示例:

import (
	"fmt"
	"html/template"
	"net/http"
)

func Main() {
	http.HandleFunc("/", escapeTest)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println("ListenAndServe Err:", err)
	}
}

func escapeTest(w http.ResponseWriter, r *http.Request) {
	text := template.HTMLEscapeString("<script>alert('aaa');</script>")
	// 輸出到客戶端
	w.Write([]byte(text))
	// 或者
	//template.HTMLEscape(w, []byte("<script>alert('aaa');</script>"))
}

輸出爲:

&lt;script&gt;alert(&#39;aaa&#39;);&lt;/script&gt;
t, _ := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}`)
t.ExecuteTemplate(w, "T", "<script>alert('aaa');</script>")

輸出爲:

hello,&lt;script&gt;alert(&#39;aaa&#39;);&lt;/script&gt;!

但若是使用template.HTML進行強轉後則:

t.ExecuteTemplate(w, "T", template.HTML("<script>alert('aaa');</script>"))

輸出爲:

hello,<script>alert('aaa');</script>!
template.HTMLEscape(w, []byte("<script>alert('aaa');</script>"))

這種方式獲得的結果是轉義了的。

可是下面獲得的倒是彈窗:

w.Write([]byte(template.HTML("<script>alert('aaa');</script>")))

文件上傳

表單上傳文件

package upload

import (
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
)

func Main() {
	http.HandleFunc("/", index)
	http.HandleFunc("/upload", upload)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println("ListenAndServe Err:", err)
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("upload/upload.gtpl")
	if err != nil {
		fmt.Println("index() Err:", err)
		return
	}
	t.Execute(w, nil)
}

func upload(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		return
	}
	r.ParseMultipartForm(32 << 20) // 32MB
	// 得到上傳文件的句柄
	// FormFile會自動調用ParseMultipartForm(),默認內存32MB
	file, header, err := r.FormFile("uploadfile") // uploadfile是<input>的name
	if err != nil {
		fmt.Println("upload() Err:", err)
		return
	}
	defer file.Close()

	// 輸出header
	fmt.Fprintf(w, "%v", header.Header)

	// 轉儲文件[upload目錄必須存在]
	f, err := os.OpenFile("./upload/"+header.Filename, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("os.OpenFile() Err:", err)
		return
	}
	defer f.Close()
	io.Copy(f, file)
}

客戶端上傳文件

package upload

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
)

func postFile(filename, url string) error {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	// 關鍵的一步
	fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
	if err != nil {
		fmt.Println("error writing to buffer")
		return err
	}

	//打開文件句柄操做
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Println("error opening file")
		return err
	}
	defer fh.Close()

	//iocopy
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return err
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	resp, err := http.Post(url, contentType, bodyBuf)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	resp_body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	fmt.Println(resp.Status)
	fmt.Println(string(resp_body))
	return nil
}

func ClientPostFile() {
	target_url := "http://localhost:9090/upload"
	filename := "./魔方公式.txt"
	postFile(filename, target_url)
}

文件下載

客戶端:

window.open("/admin/account/download/" + time[0] + "/" + time[1], "_blank");

服務端:

c.Header("Content-Disposition", `attachment;filename="`+url.QueryEscape(fileName)+`"`)
c.File("recordStatisFiles/" + fileName)

Content-Type在ServerFile中會自動添加。

Session和Cookie

設置和讀取Cookie

import (
	"log"
	"net/http"
	"time"
)

func Main() {
	http.HandleFunc("/w", writeCookie)
	http.HandleFunc("/r", readCookie)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("listener:", err)
	}
}

func writeCookie(w http.ResponseWriter, r *http.Request) {
	expiration := time.Now()
	expiration = expiration.AddDate(0, 0, 1)
	cookie := http.Cookie{Name: "name", Value: "chen", Expires: expiration}
	http.SetCookie(w, &cookie)
}

func readCookie(w http.ResponseWriter, r *http.Request) {
	cookies := r.Cookies()
	for _, cookie := range cookies {
		w.Write([]byte(cookie.String() + "\n"))
	}
	cookie, _ := r.Cookie("name")
	w.Write([]byte("name的cookie:" + cookie.String()))
}

Session

  • 目前Go標準包沒有爲session提供支持
相關文章
相關標籤/搜索