我定义了两个功能,它们的功能略有不同,但在语法上是相同的。

有问题的函数将POST请求发送到api。

复制发生在构造请求,添加 header 等时。

我如何重构代码以删除所说的重复项。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httputil"
)

type token struct {
    Token string
}

type config struct {
    Foo string
}

func main() {
    token, err := getAuthToken()
    if err != nil {
        log.Fatal(err)
    }

    config, err := getConfig("foo", token)
    if err != nil {
        log.Fatal(err)
    }

    _ = config
}

func getAuthToken() (string, error) {
    endpoint := "foo"

    body := struct {
        UserName string `json:"username"`
        Password string `json:"password"`
    }{
        UserName: "foo",
        Password: "bar",
    }

    jsnBytes, err := json.Marshal(body)
    if err != nil {
        return "", err

    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
    if err != nil {
        return "", fmt.Errorf("Unable to create request. %v", err)

    }

    req.Header.Add("Content-Type", "application/json")

    dump, err := httputil.DumpRequest(req, true)
    if err != nil {
        return "", fmt.Errorf("Could not dump request. ", err)
    }

    log.Println("Request: ", string(dump))

    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return "", fmt.Errorf("HTTP Error: %v", err)
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", fmt.Errorf("Error reading response body: %v", err)
    }

    var token token

    err = json.Unmarshal(bytes, &token)
    if err != nil {
        return "", fmt.Errorf("Could not unamrshal json. ", err)
    }

    return token.Token, nil
}

func getConfig(id string, token string) (*config, error) {
    endpoint := "foo"

    body := struct {
        ID string `json:"id"`
    }{
        ID: id,
    }

    jsnBytes, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
    if err != nil {
        return nil, fmt.Errorf("Unable to create request. %v", err)
    }

    req.Header.Add("Authorization", "Bearer "+token)
    req.Header.Add("Content-Type", "application/json")

    dump, err := httputil.DumpRequest(req, true)
    if err != nil {
        return nil, fmt.Errorf("Could not dump request. ", err)
    }

    log.Println("Request: ", string(dump))

    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("HTTP Error: %v", err)
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("Error reading response body: %v", err)
    }

    var config config

    err = json.Unmarshal(bytes, &config)
    if err != nil {
        return nil, fmt.Errorf("Could not unamrshal json. ", err)
    }

    return &config, nil
}

最佳答案

我要说的是,发送请求的本质是您正在将主体发送到端点并解析结果。然后, header 是 optional ,您可以一路添加到请求中。考虑到这一点,我将使用单个通用函数来发送带有此签名的请求:

type option func(*http.Request)
func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {

请注意,这是使用功能选项的,Dave Cheney在此处做了出色的描述:

https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

完整的代码将变为:

https://play.golang.org/p/GV6FeipIybA
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httputil"
)

type token struct {
    Token string
}

type config struct {
    Foo string
}

func main() {
    token, err := getAuthToken()
    if err != nil {
        log.Fatal(err)
    }

    config, err := getConfig("foo", token)
    if err != nil {
        log.Fatal(err)
    }

    _ = config
}

func getAuthToken() (string, error) {
    endpoint := "foo"

    body := struct {
        UserName string `json:"username"`
        Password string `json:"password"`
    }{
        UserName: "foo",
        Password: "bar",
    }
    var token token

    err := sendRequest(endpoint, body, &token)
    if err != nil {
        return "", err
    }

    return token.Token, nil
}

func getConfig(id string, token string) (*config, error) {
    endpoint := "foo"

    body := struct {
        ID string `json:"id"`
    }{
        ID: id,
    }
    var config config

    err := sendRequest(endpoint, body, &config, header("Content-Type", "application/json"))
    if err != nil {
        return nil, err
    }

    return &config, nil
}

type option func(*http.Request)
func header(key, value string) func(*http.Request) {
    return func(req *http.Request) {
        req.Header.Add(key, value)
    }
}

func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {
    jsnBytes, err := json.Marshal(body)
    if err != nil {
        return err

    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
    if err != nil {
        return fmt.Errorf("Unable to create request. %v", err)

    }

    req.Header.Add("Content-Type", "application/json")
    for _, option := range options {
        option(req)
    }


    dump, err := httputil.DumpRequest(req, true)
    if err != nil {
        return fmt.Errorf("Could not dump request. ", err)
    }

    log.Println("Request: ", string(dump))

    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("HTTP Error: %v", err)
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return fmt.Errorf("Error reading response body: %v", err)
    }

    err = json.Unmarshal(bytes, result)
    if err != nil {
        return fmt.Errorf("Could not unamrshal json. ", err)
    }
    return nil
}

09-25 17:50