以 Go 語言快速實作 HTTP GET/POST API

維基百科中提到:

目前在三種主流的Web服務實現方案中,因為REST模式與複雜的SOAP和XML-RPC相比更加簡潔,越來越多的web服務開始採用REST風格設計和實現。
...
符合 REST 設計風格的 Web API 稱為 RESTful API。它從以下三個方面資源進行定義:直觀簡短的資源地址、傳輸的資源、對資源的操作。

那麼我們要如何使用 Go 語言原生的函式庫建立簡單的 Web Server 提供 GET/POST 接口呢?讓我們透過 Hello World 的範例來說明。

建立 HTTP Server 接收請求

  1. 透過 http.HandleFunc("/", callback) ,我們可以定義 callback 函式所要負責的 URI。
  2. 呼叫 http.Request.ParseForm() 取出參數。同時適用於 GET/POST,所以為了減少行數我將範例中 GET/POST 一併處理。
  3. 透過 http.Request.FormValue("key") 取出姓氏與名字。
  4. 最後,將字串以 http.ResponseWriter.Write("Hello World") 輸出,client 端即可在網頁上看到對應的訊息囉!
// server.go
package main

import (  
    "fmt"
    "net/http"
)

func main() {  
    http.HandleFunc("/cepave", func(w http.ResponseWriter, req *http.Request) {
        req.ParseForm()
        if req.Method == "GET" || req.Method == "POST" {
            fmt.Println(req.ContentLength)
            firstname := req.FormValue("firstname")
            lastname := req.FormValue("lastname")
            w.Write([]byte(fmt.Sprintf("[%s] Hello, %s %s!", req.Method, firstname, lastname)))
        } else {
            http.Error(w, "The method is not allowed.", http.StatusMethodNotAllowed)
        }
    })

    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        fmt.Println("ListenAndServe failed: ", err)
    }
}

實際發送請求來試試!

完成了 Server 端程式之後,我們可以透過 go run server.go 開始運行。 HTTP GET 可以直接以瀏覽器輸入 URL 即可測試,但既然都寫了 server.go 不如也把 client.go 實現吧!

GET/POST 發送請由分別是透過 http.Get(...) 以及 http.PostForm(...) 來達成。在 client.go 裡面之所以多 import 兩個函式庫的原因是為了要儲存 HTTP POST 的參數,並且處理送出請求後返回的 HTTP response。

// client.go
package main

import (  
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

func main() {  
    // POST
    resp, err := http.PostForm("http://localhost:9000/cepave",
        url.Values{"firstname": {"Kordan"}, "lastname": {"Ou"}})
    if err != nil {
        fmt.Println(err)
    } else {
        body, _ := ioutil.ReadAll(resp.Body)
        fmt.Println("POST OK: ", string(body), resp)
    }

    // GET
    resp, err = http.Get("http://localhost:9000/cepave?firstname=Kordan&lastname=Ou")
    if err != nil {
        fmt.Println(err)
    } else {
        body, _ := ioutil.ReadAll(resp.Body)
        fmt.Println("GET OK: ", string(body), resp)
    }

}

如果只是想測試 API,我們也可以打開終端機直接輸入以下命令:

$ curl "localhost:9000/cepave?firstname=Kordan&lastname=Ou"
[GET] Hello, Kordan Ou!

$curl -X POST -d "firstname=Kordan&&lastname=Ou" localhost:9000/cepave
[POST] Hello, Kordan Ou!

如果是有巢狀結構的 JSON,我們可以這樣發 HTTP POST:

package main

import (  
    "bytes"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {  
    var jsonStr = []byte(`{
    "endpoint_counters": [
        {
            "endpoint": "host1",
            "counter": "load.1min"
        },
        {
            "endpoint": "host2",
            "counter": "cpu.idle"
        }
    ],
    "start": 1437375109,
    "end": 1437377109,
    "cf": "AVERAGE"
    }`)

    url := "http://example.com/graph/history"
    fmt.Println("URL:>", url)

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Set("X-Custom-Header", "myvalue")
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    fmt.Println("response Status:", resp.Status)
    fmt.Println("response Headers:", resp.Header)
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("response Body:", string(body))

}

透過以上幾個範例,應該可以感受到 Go 語言的簡潔以及內建函式庫的強大吧?!

其他參考資料

  1. http://www.restapitutorial.com/lessons/httpmethods.html
  2. http://www.evanlin.com/golangheroku-rest-api-web-application/
  3. http://dougblack.io/words/a-restful-micro-framework-in-go.html