使用Golang的http包调用异步API的方法探讨

使用Golang的http包调用异步API的方法探讨

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

type Token struct {
	TokenType    string `json:"token_type"`
	Scope        string `json:"scope"`
	ExpiresIn    uint   `json:"expires_in"`
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	IdToken      string `json:"id_token"`
}

func main() {

	tenant := "https://example.com/adfs"
	url := tenant + "/oauth2/token"
	
	payload := strings.NewReader("grant_type=client_credentials&tenant_id=adxxxa2e&client_secret=45xxxN9&resource=https://ax.d365ffo.onprem.xxx.com")

	// payload := strings.NewReader("grant_type=password&client_id=a1xxxa2e&scope=user.read openid profile offline_access&client_secret=45xxxN9&username=xxx%40xxx.com&password=xxx")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	var data Token
	json.Unmarshal(body, &data)
	token := fmt.Sprintf("%v %v\n", data.TokenType, data.AccessToken)

	Employees(token)
}

func Employees(token string) {

	fmt.Println(token)

	url := "https://ax.d365ffo.onprem.xxx.com/namespaces/AXSF/data/Employees"

	req, _ := http.NewRequest("GET", url, nil)

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

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close() // Getting error at  this
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))
}

我得到的输出是:

PS D:\goD365> go run token.go
bearer eyJ0eX*******************************************PZs4guAQ

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x40 pc=0x36df4d]

goroutine 1 [running]:
main.Employees({0xc000162e00, 0x373})
        D:/goD365/token.go:67 +0x34d  // => defer res.Body.Close() // 从底部数第4行
main.main()
        D:/goD365/token.go:42 +0x2f4  // => Employees(token)
exit status 2
PS D:\goD365> 

使用 Postman 得到的输出是:

{
  "@odata.context": "https://ax.d365ffo.onprem.xxx.com/namespaces/AXSF/data/$metadata#Employees",
  "value": [
    {
      "@odata.etag": "W/\"JzEyxxxxJw==\"",
      "PersonnelNumber": "xxxx",
      "EmploymentStartDate": "2018-12-31T21:00:00Z",
      "LastName": "xx",
      "NativeLanguageId": "EN",
      "FirstName": "xx",
    },
    {
      "@odata.etag": "W/\"JzEyxxxxJw==\"",
      "PersonnelNumber": "xxxx",
      "EmploymentStartDate": "2018-12-31T21:00:00Z",
      "LastName": "xx",
      "NativeLanguageId": "EN",
      "FirstName": "xx",
    },
}

由于响应是 OData 格式,我假设 API 类型是异步的,这可能是我的代码失败的原因(但我不确定我的理解是否正确,这可能是一个XY问题)。

请提供帮助!


更多关于使用Golang的http包调用异步API的方法探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

hyousef:

res, _ := http.DefaultClient.Do(req)

请检查返回的错误。res 很可能为 nil,除非 err 不为 nil,否则这种情况不应该发生。

更多关于使用Golang的http包调用异步API的方法探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从错误信息来看,问题不是API异步导致的,而是HTTP请求失败导致res为nil。当http.DefaultClient.Do()返回错误时,res会是nil,调用res.Body.Close()就会引发空指针异常。

以下是修正后的代码:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

type Token struct {
	TokenType    string `json:"token_type"`
	Scope        string `json:"scope"`
	ExpiresIn    uint   `json:"expires_in"`
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	IdToken      string `json:"id_token"`
}

func main() {
	tenant := "https://example.com/adfs"
	url := tenant + "/oauth2/token"
	
	payload := strings.NewReader("grant_type=client_credentials&tenant_id=adxxxa2e&client_secret=45xxxN9&resource=https://ax.d365ffo.onprem.xxx.com")

	req, err := http.NewRequest("POST", url, payload)
	if err != nil {
		fmt.Printf("创建请求失败: %v\n", err)
		return
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Printf("获取token失败: %v\n", err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Printf("读取响应失败: %v\n", err)
		return
	}

	var data Token
	if err := json.Unmarshal(body, &data); err != nil {
		fmt.Printf("解析token失败: %v\n", err)
		return
	}
	
	if data.AccessToken == "" {
		fmt.Printf("未获取到access token: %s\n", string(body))
		return
	}
	
	token := fmt.Sprintf("%s %s", data.TokenType, data.AccessToken)
	Employees(token)
}

func Employees(token string) {
	fmt.Printf("Token: %s\n", token)

	url := "https://ax.d365ffo.onprem.xxx.com/namespaces/AXSF/data/Employees"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		fmt.Printf("创建员工请求失败: %v\n", err)
		return
	}

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

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Printf("获取员工数据失败: %v\n", err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Printf("读取员工响应失败: %v\n", err)
		return
	}

	fmt.Printf("状态码: %d\n", res.StatusCode)
	fmt.Printf("响应体: %s\n", string(body))
}

对于异步API调用,如果确实需要处理异步响应,可以使用goroutine和channel:

func EmployeesAsync(token string) {
	url := "https://ax.d365ffo.onprem.xxx.com/namespaces/AXSF/data/Employees"
	
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		fmt.Printf("创建请求失败: %v\n", err)
		return
	}
	
	req.Header.Add("Authorization", token)
	req.Header.Add("Accept", "application/json")
	
	// 使用goroutine进行异步调用
	resultChan := make(chan []byte)
	errorChan := make(chan error)
	
	go func() {
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			errorChan <- err
			return
		}
		defer res.Body.Close()
		
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			errorChan <- err
			return
		}
		
		resultChan <- body
	}()
	
	// 等待结果
	select {
	case body := <-resultChan:
		fmt.Printf("异步响应: %s\n", string(body))
	case err := <-errorChan:
		fmt.Printf("异步错误: %v\n", err)
	}
}

主要修改:

  1. 添加了所有可能返回错误的错误处理
  2. 在调用res.Body.Close()前检查res是否为nil
  3. 验证了access token是否为空
  4. 添加了异步调用示例(如果需要真正的异步处理)
回到顶部