go 搭建web服務


Go語言標准庫 - net/http

在學習Go語言有一個很好的起點,Go語言官方文檔很詳細,今天我們學習的Go Web服務器的搭建就需要用到Go語言官方提供的標准庫 net/http,通過http包提供了HTTP客戶端和服務端的實現。同時使用這個包能很簡單地對web的路由,靜態文件,模版,cookie等數據進行設置和操作。

下面是用http包建立web服務器的例子:

1、使用http.HandleFunc

package main
import (
    "fmt"
    "net/http"
    "strings"
    "log"
)
func doRequest(w http.ResponseWriter, r *http.Request) {
    r.ParseForm() //解析參數,默認是不會解析的
    fmt.Println(r.Form) //這些信息是輸出到服務器端的打印信息
    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 Wrold!") //這個寫入到w的是輸出到客戶端的
}
func main() {
    http.HandleFunc("/", doRequest) //設置訪問的路由
    err := http.ListenAndServe(":9090", nil) //設置監聽的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

上面的代碼我們在IDE中編譯后並運行成功后,這個時侯我們就可以在9090端口監聽http鏈接請求了。

 

 我們看到了上面的代碼,要編寫一個Web服務器是不是很簡單,只要調用http包的兩個函數就可以了。

 在main函數中,我們從net/http包中調用了一個http.HandleFucn函數來注冊一個處理函數,這個函數接受兩個參數。第一個是字符串,這個就是進行路由匹配,我這里是/路由。第二個參數是一個func (ResponseWriter, Request)的簽名。

我們的doRequest函數就是這樣的簽名。下一行中的http.ListenAndServe(":8000", nil),表示監聽localhost的9090端口,暫時忽略掉nil。

在doRequest函數中我們有兩個參數,一個是http.ResponseWriter類型的。它類似響應流,實際上是一個接口類型。

第二個是http.Request類型,類似於HTTP 請求。我們不必使用所有的參數,如果只是簡單的輸出,那么我們只需要使用http.ResponseWriter,io.WriteString,將會把輸出流寫入數據。

我們再稍微改下,大家請注意修改的部分(這里我們只調整 main函數部分代碼)

2、使用ServeMux

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", doRequest)
    err :=  http.ListenAndServe(":9090", mux) //設置監聽的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

這個例子中,我們不再在函數http.ListenAndServe使用nil了。這個例子跟上面的例子其實是一樣的。使用http注冊hanlder 函數模式就是用的ServeMux。

3、使用http.Server,而不是ServeMux。它們都是用net/http包運行了服務器。

package main

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

var mux map[string]func(http.ResponseWriter, *http.Request)

func main() {
    server := http.Server{
        Addr:    ":8000",
        Handler: &doHandler{},
    }

    mux = make(map[string]func(http.ResponseWriter, *http.Request))
    mux["/test"] = doRequest

    err := server.ListenAndServe()
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

type doHandler struct{}

func (*doHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if res, ok := mux[r.URL.String()]; ok {
        res(w, r)
        return
    }

    io.WriteString(w, "url params: "+r.URL.String())
}
func doRequest(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()                      //解析url傳遞的參數,對於POST則解析響應包的主體(request body)
    fmt.Fprintf(w, "service start...") //這個寫入到w的是輸出到客戶端的 也可以用下面的 io.WriteString對象
}

如果以前你是.NET程序員或java,那你也許就會問,我們的IIS服務器,tomcat服務器不需要嗎?Go就是不需要這些,因為他直接就監聽了TCP端口了。

二、Web工作方式的幾個概念

我們看到Go通過簡單的幾行代碼就已經運行起來一個Web服務了,而且這個Web服務內部有支持高並發的特性。現在Web服務已經搭建完成了,那我們現在來了解一個這個服務是怎么運行起來的呢?

以下幾個為服務器段的概念

  • Request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
  • Response:服務器需要反饋給客戶端的信息
  • Conn:用戶的每次請求鏈接
  • Handler:處理請求和生成返回信息的處理邏輯

分析http包運行機制

 

 Go實現Web服務的工作模式流程圖

這個過程我們需要清楚以下三個問題,則就清楚Go是如何讓Web運行起來了

    • 如何監聽端口?
      通過上面的代碼我們看到Go是通過一個函數ListenAndServe來處理這些事情的,這個底層其實這樣處理的:初始化一個server對象,然后調用了net.Listen("tcp", addr),也就是底層用TCP協議搭建了一個服務,然后監控我們設置的端口。

      Go http包的源碼,這里我們可以看到整個http處理過程

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
        if srv.ReadTimeout != 0 {
            rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
        }
        if srv.WriteTimeout != 0 {
            rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout))
        }
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        go c.serve()
    }
    panic("not reached")
}

 

    • 如何接收客戶端請求?
      上面代碼執行監控端口之后,調用了srv.Serve(net.Listener)函數,這個函數就是處理接收客戶端的請求信 息。這個函數里面起了一個for{},首先通過Listener接收請求,其次創建一個 Conn,最后單獨開了一個 goroutine,把這個請求的數據當做參數扔給這個conn去服務:go c.serve()。這 個就是高並發體現了, 用戶的每一次請求都是在一個新的goroutine去服務,相互不影響。
    • 如何分配handler?
      conn首先會解析request:c.readRequest(),然后獲取相應的handler:handler := c.server.Handler,也就是我們剛才在調用函數ListenAndServe時候的第二個參數,我們前面例子傳遞的是nil,也就是為空,那么默認獲取handler = DefaultServeMux,那么這個變量用來做什么的呢?對,這個變量就是一個路由器,它用來匹配url跳轉到其相應的handle函數,那么這個我們有設置過嗎?有,我們調用的代碼里面第一句不是調用了http.HandleFunc("/", sayhelloName)嘛。這個作用就是注冊了請求/的路由規則,當請求uri為"/",路由就會轉到函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這個方法內部其實就是調用sayhelloName本身,最后通過寫入response的信息反饋到客戶端。

一個http連接處理流程

至此我們的三個問題已經全部得到了解答,你現在對於Go如何讓Web跑起來的是否已經基本了解。

 

轉:https://www.cnblogs.com/franklee97/p/7131551.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM