消费分页的REST API

huangapple go评论109阅读模式
英文:

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:

{
    &quot;hasNextPage&quot;: true,
    &quot;endCursor&quot;: &quot;some_guid_present_here&quot;
}

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 (
	&quot;encoding/json&quot;
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
    &quot;fmt&quot;
)

type Request struct {
    Url string
    ApiKey string
}

type Response struct {
    ...some fields...
	Paginations Pagination `json:&quot;pagination&quot;`
}

type Pagination struct {
	EndCursor   string `json:&quot;endCursor&quot;`
	HasNextPage bool   `json:&quot;hasNextPage&quot;`
}


func (request *Request) Get() ([]Response, error) {
    
    var responses []Response
    var response Response

	// Set up new request
	req, err := http.NewRequest(&quot;GET&quot;, request.Url, nil)
	if err != nil {
		fmt.Println(&quot;Error creating request...&quot;)
		return responses, err
	}

	// Add request headers
	req.Header = http.Header{
		&quot;accept&quot;:        {&quot;application/json&quot;},
		&quot;authorization&quot;: {&quot;Bearer &quot; + 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, &amp;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(&quot;Has Next Page? %t\n&quot;, 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(&quot;GET&quot;, request.Url, nil)
		if err != nil {
			return responses, err
		}

		// Append &quot;after&quot; cursor to query in order to redirect to paginated response
		qry := req.URL.Query()
		qry.Set(&quot;after&quot;, response.Paginations.EndCursor)
		req.URL.RawQuery = qry.Encode()
		fmt.Println(&quot;Paginated request query: &quot;, req.URL.String())

		// Make request
		resp, err := http.DefaultClient.Do(req)
		response.Status = resp.StatusCode
		fmt.Printf(&quot;Status Code: %d\n&quot;, response.Status)

		// Read response and deserialize it
		respBody, err := ioutil.ReadAll(resp.Body)
		json.Unmarshal(respBody, &amp;response)
		fmt.Println(&quot;Pagination Cursor: &quot;, response.Paginations.EndCursor)
		fmt.Printf(&quot;Has Next Page? %t\n&quot;, 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 (
    &quot;fmt&quot;
	&quot;sync&quot;
)

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(&quot;Incremented wait group&quot;)
		go func(i Request) {
			defer wg.Done() // Decrement
			resp, err := req.Get()
			if err != nil {
				fmt.Println(err)
			}
			ch &lt;- resp
			fmt.Println(&quot;Decremented wait group&quot;)
		}(req)
	}

	results := make([][]Response, len(etl.Requests))
	for i, _ := range results {
		results[i] = &lt;-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 (
&quot;encoding/json&quot;
&quot;io/ioutil&quot;
&quot;net/http&quot;
&quot;fmt&quot;
)
type Request struct {
Url string
ApiKey string
}
type Response struct {
...some fields...
Paginations Pagination `json:&quot;pagination&quot;`
}
type Pagination struct {
EndCursor   string `json:&quot;endCursor&quot;`
HasNextPage bool   `json:&quot;hasNextPage&quot;`
}
func (request *Request) Get() ([]Response, error) {
var responses []Response
var response Response
// Set up new request
req, err := http.NewRequest(&quot;GET&quot;, request.Url, nil)
if err != nil {
fmt.Println(&quot;Error creating request...&quot;)
return responses, err
}
// Add request headers
req.Header = http.Header{
&quot;accept&quot;:        {&quot;application/json&quot;},
&quot;authorization&quot;: {&quot;Bearer &quot; + 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, &amp;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(&quot;Has Next Page? %t\n&quot;, 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 &quot;after&quot; cursor to query in order to redirect to paginated response
qry := req.URL.Query()
qry.Set(&quot;after&quot;, response.Paginations.EndCursor)
req.URL.RawQuery = qry.Encode()
fmt.Println(&quot;Paginated request query: &quot;, req.URL.String())
// Make request
resp, err := http.DefaultClient.Do(req)
response.Status = resp.StatusCode
fmt.Printf(&quot;Status Code: %d\n&quot;, response.Status)
// Read response and deserialize it
respBody, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(respBody, &amp;response)
fmt.Println(&quot;Pagination Cursor: &quot;, response.Paginations.EndCursor)
fmt.Printf(&quot;Has Next Page? %t\n&quot;, 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
}

huangapple
  • 本文由 发表于 2023年1月25日 04:41:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75227071.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定