使用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)
}
}
主要修改:
- 添加了所有可能返回错误的错误处理
- 在调用
res.Body.Close()前检查res是否为nil - 验证了access token是否为空
- 添加了异步调用示例(如果需要真正的异步处理)

