英文:
Consuming Paginated REST API
问题
我正在尝试使用一个API,每次成功请求后都会返回以下json键:
{
"hasNextPage": true,
"endCursor": "some_guid_present_here"
}
API的工作方式是检查是否有下一页,然后使用适当的游标继续分页。
然而,每次使用这个结构时,即使response.Paginations.HasNextPage
为false,它有时仍会保持循环。
我正在尝试理解是否是我以这种方式消耗分页API的结构导致了这个问题。
此外,我有4-5个起始请求,我通过goroutines分别发送。我不确定这是否会引起问题,但我也在etl.go
中附上了这个。
主要的请求结构在api.go
中。
我已经确认我收到了响应,并且它们正确地解组,但我正在努力解决这种不确定的行为。
api.go
package models
import (
"encoding/json"
"io/ioutil"
"net/http"
"fmt"
)
type Request struct {
Url string
ApiKey string
}
type Response struct {
...一些字段...
Paginations Pagination `json:"pagination"`
}
type Pagination struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (request *Request) Get() ([]Response, error) {
var responses []Response
var response Response
// 设置新请求
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
fmt.Println("创建请求时出错...")
return responses, err
}
// 添加请求头
req.Header = http.Header{
"accept": {"application/json"},
"authorization": {"Bearer " + request.ApiKey},
}
// 从API获取初始响应并捕获状态码
resp, _ := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
// 读取响应体并解组为结构体
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
// 如果有解析错误,记录下来
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// 这个字段将在响应有效负载中
// 它已验证为bool类型(而不是字符串)
fmt.Printf("是否有下一页?%t\n", response.Paginations.HasNextPage)
// 将响应追加到响应列表中
responses = append(responses, response)
// 如果有分页,继续循环直到所有分页都用完
for response.Paginations.HasNextPage == true {
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
return responses, err
}
// 在查询中添加“after”游标,以便重定向到分页响应
qry := req.URL.Query()
qry.Set("after", response.Paginations.EndCursor)
req.URL.RawQuery = qry.Encode()
fmt.Println("分页请求查询:", req.URL.String())
// 发送请求
resp, err := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
fmt.Printf("状态码:%d\n", response.Status)
// 读取响应并反序列化
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
fmt.Println("分页游标:", response.Paginations.EndCursor)
fmt.Printf("是否有下一页?%t\n", response.Paginations.HasNextPage)
// 如果有解析错误,记录下来
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// 将响应追加到响应列表中
responses = append(responses, response)
}
return responses, nil
}
etl.go
package models
import (
"fmt"
"sync"
)
type Etl struct {
Requests []Request
}
func (etl *Etl) Extract() [][]Response {
var wg sync.WaitGroup
ch := make(chan []Response)
defer close(ch)
for _, req := range etl.Requests {
wg.Add(1) // 增加计数
fmt.Println("增加等待组")
go func(i Request) {
defer wg.Done() // 减少计数
resp, err := req.Get()
if err != nil {
fmt.Println(err)
}
ch <- resp
fmt.Println("减少等待组")
}(req)
}
results := make([][]Response, len(etl.Requests))
for i, _ := range results {
results[i] = <-ch
//fmt.Println(results[i])
}
wg.Wait()
return nil
}
希望对你有所帮助!
英文:
I am trying to consume an API that on each successful request has a json key like the following:
{
"hasNextPage": true,
"endCursor": "some_guid_present_here"
}
The way the API works (I have used it many times in Python but am trying with Go for separate use case) is to basically check if there is a next page, then use the appropriate cursor to continue to paginate.
However, every time I use this structure, it will sometimes keep looping even though the response.Paginations.HasNextPage
will be false.
I am trying to understand if it is my structure of consuming a paginated API like this that is causing this or else.
Also, I have say 4-5 requests to start off with, which I sent separately via goroutines. I am not sure if this is causing an issue but I've attached that as well in etl.go
.
The main request making structs are in api.go
.
I've confirmed that I do receive responses and they are unmarshaling properly, but am trying to wrangle this uncertain behavior.
api.go
package models
import (
"encoding/json"
"io/ioutil"
"net/http"
"fmt"
)
type Request struct {
Url string
ApiKey string
}
type Response struct {
...some fields...
Paginations Pagination `json:"pagination"`
}
type Pagination struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (request *Request) Get() ([]Response, error) {
var responses []Response
var response Response
// Set up new request
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
fmt.Println("Error creating request...")
return responses, err
}
// Add request headers
req.Header = http.Header{
"accept": {"application/json"},
"authorization": {"Bearer " + request.ApiKey},
}
// Get our initial response from the API and capture status code
resp, _ := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
// Read the response body and Unmarshal into struct
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
// If there was a parsing error, log it
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// This field will be in the response payload
// It is verified to be of type bool (not string)
fmt.Printf("Has Next Page? %t\n", resp.Paginations.HasNextPage)
// Append response to our slice of responses
responses = append(responses, response)
// If paginations are available, continue to loop through until all paginations are exhausted
for response.Paginations.HasNextPage == true {
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
return responses, err
}
// Append "after" cursor to query in order to redirect to paginated response
qry := req.URL.Query()
qry.Set("after", response.Paginations.EndCursor)
req.URL.RawQuery = qry.Encode()
fmt.Println("Paginated request query: ", req.URL.String())
// Make request
resp, err := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
fmt.Printf("Status Code: %d\n", response.Status)
// Read response and deserialize it
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
fmt.Println("Pagination Cursor: ", response.Paginations.EndCursor)
fmt.Printf("Has Next Page? %t\n", response.Paginations.HasNextPage)
// If there was a parsing error, log it
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// Append response to our slice of responses
responses = append(responses, response)
}
return responses, nil
}
etl.go
package models
import (
"fmt"
"sync"
)
type Etl struct {
Requests []Request
}
func (etl *Etl) Extract() [][]Response {
var wg sync.WaitGroup
ch := make(chan []Response)
defer close(ch)
for _, req := range etl.Requests {
wg.Add(1) // Increment
fmt.Println("Incremented wait group")
go func(i Request) {
defer wg.Done() // Decrement
resp, err := req.Get()
if err != nil {
fmt.Println(err)
}
ch <- resp
fmt.Println("Decremented wait group")
}(req)
}
results := make([][]Response, len(etl.Requests))
for i, _ := range results {
results[i] = <-ch
//fmt.Println(results[i])
}
wg.Wait()
return nil
}
答案1
得分: 1
我相信我找到了问题所在。在我的分页循环中,for response.Paginations.HasNextPage == true
,我在每次迭代中创建了一个新的请求对象(http.NewRequest
),该对象没有添加前一个(初始请求)的标头。
这导致返回401未经授权错误并持续查询API,因为它没有接收到新的response.Paginations.HasNextPage
值。
我的解决方案是稍微修改for循环,如下所示:
package models
import (
"encoding/json"
"io/ioutil"
"net/http"
"fmt"
)
type Request struct {
Url string
ApiKey string
}
type Response struct {
// ...一些字段...
Paginations Pagination `json:"pagination"`
}
type Pagination struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (request *Request) Get() ([]Response, error) {
var responses []Response
var response Response
// 设置新的请求
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
fmt.Println("创建请求时出错...")
return responses, err
}
// 添加请求标头
req.Header = http.Header{
"accept": {"application/json"},
"authorization": {"Bearer " + request.ApiKey},
}
// 从API获取初始响应并捕获状态码
resp, _ := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
// 读取响应正文并解组为结构体
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
// 如果有解析错误,记录下来
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// 这个字段将在响应有效负载中
// 它被验证为bool类型(而不是字符串)
fmt.Printf("是否有下一页?%t\n", response.Paginations.HasNextPage)
// 将响应附加到我们的响应切片中
responses = append(responses, response)
// 如果有分页可用,继续循环直到所有分页用尽
for response.Paginations.HasNextPage == true {
// 将“after”游标附加到查询中,以便重定向到分页响应
qry := req.URL.Query()
qry.Set("after", response.Paginations.EndCursor)
req.URL.RawQuery = qry.Encode()
fmt.Println("分页请求查询:", req.URL.String())
// 发送请求
resp, err := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
fmt.Printf("状态码:%d\n", response.Status)
// 读取响应并反序列化
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
fmt.Println("分页游标:", response.Paginations.EndCursor)
fmt.Printf("是否有下一页?%t\n", response.Paginations.HasNextPage)
// 如果有解析错误,记录下来
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// 将响应附加到我们的响应切片中
responses = append(responses, response)
}
return responses, nil
}
以上是翻译好的内容,请查阅。
英文:
I believe I found the issue. In my pagination loop for response.Paginations.HasNextPage == true
I was creating a new request object (http.NewRequest
) on each iteration which did not have headers added from the prior (initial request).
This caused a 401 unauthorized error to return and continuous querying of the API since it was not receiving a new response.Paginations.HasNextPage
value.
My solution was to simply alter the for loop a bit like so:
package models
import (
"encoding/json"
"io/ioutil"
"net/http"
"fmt"
)
type Request struct {
Url string
ApiKey string
}
type Response struct {
...some fields...
Paginations Pagination `json:"pagination"`
}
type Pagination struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
}
func (request *Request) Get() ([]Response, error) {
var responses []Response
var response Response
// Set up new request
req, err := http.NewRequest("GET", request.Url, nil)
if err != nil {
fmt.Println("Error creating request...")
return responses, err
}
// Add request headers
req.Header = http.Header{
"accept": {"application/json"},
"authorization": {"Bearer " + request.ApiKey},
}
// Get our initial response from the API and capture status code
resp, _ := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
// Read the response body and Unmarshal into struct
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
// If there was a parsing error, log it
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// This field will be in the response payload
// It is verified to be of type bool (not string)
fmt.Printf("Has Next Page? %t\n", resp.Paginations.HasNextPage)
// Append response to our slice of responses
responses = append(responses, response)
// If paginations are available, continue to loop through until all paginations are exhausted
for response.Paginations.HasNextPage == true {
// Append "after" cursor to query in order to redirect to paginated response
qry := req.URL.Query()
qry.Set("after", response.Paginations.EndCursor)
req.URL.RawQuery = qry.Encode()
fmt.Println("Paginated request query: ", req.URL.String())
// Make request
resp, err := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
fmt.Printf("Status Code: %d\n", response.Status)
// Read response and deserialize it
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &response)
fmt.Println("Pagination Cursor: ", response.Paginations.EndCursor)
fmt.Printf("Has Next Page? %t\n", response.Paginations.HasNextPage)
// If there was a parsing error, log it
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
// Append response to our slice of responses
responses = append(responses, response)
}
return responses, nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论