Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Primary and secondary rate-limit handling #52

Merged
merged 41 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0f7fedd
Initial commit of rate limit playground
kfcampbell Mar 5, 2024
b045486
Work in progress handler sketch
kfcampbell Mar 6, 2024
671d074
RateLimitHandler slouching toward usefulness
kfcampbell Mar 7, 2024
46e0476
More progress on rate limiting using Kiota handlers
kfcampbell Mar 7, 2024
544b02f
initial implementation of rate-limiting
kfcampbell Mar 7, 2024
592029d
Remove now-unncessary read and write delays from rate limit handler
kfcampbell Mar 7, 2024
3d9bab4
Fix linting error
kfcampbell Mar 7, 2024
2cbc011
Add example to trigger rate limits
kfcampbell Mar 20, 2024
10e16e2
First super sloppy attempt at batching requests to hit rate limit
kfcampbell Mar 20, 2024
3119782
Step towards concurrency
kfcampbell Mar 20, 2024
7831688
Try using errGroup instead of waitGroup
kfcampbell Mar 20, 2024
733377a
Trigger rate limits correctly with example
kfcampbell Mar 25, 2024
4f5d965
Remove extraneous comment
kfcampbell Mar 25, 2024
314b497
Fix captured index issue
kfcampbell Mar 25, 2024
f7a907b
Ditch pagination; there's no way to access link headers right now
kfcampbell Mar 26, 2024
905845e
Small Go ecosystem cleanup things
kfcampbell Mar 26, 2024
4bed1df
Fix bug in DOS testing script and one in rate limit timing logs
kfcampbell Mar 27, 2024
486dfaa
Log primary rate limit remaining
kfcampbell Mar 27, 2024
80b5407
Add example of link header usage
kfcampbell Mar 28, 2024
6e42fd9
Added tests for rate limit handlers
kfcampbell Mar 28, 2024
81b4624
Additional test coverage and tweaks for rate limit handlers
kfcampbell Mar 28, 2024
7be37da
Additional test cases
kfcampbell Mar 28, 2024
1cfaa0c
Add tests that use mocked server
kfcampbell Mar 28, 2024
f45abc8
Added additional test case for primary rate limiting
kfcampbell Mar 29, 2024
c4b571a
Fix logic about secondary rate limit detection
kfcampbell Mar 29, 2024
dfe57c8
Introduce new abstractions to prettify and simplify client instantiation
kfcampbell Mar 29, 2024
a639720
Move rate limit test code to main.go
kfcampbell Mar 29, 2024
198ed5d
Refactor to use cmd/ directory for commands
kfcampbell Mar 29, 2024
a54283f
Merge branch 'main' into rate-limit-playground
kfcampbell Mar 29, 2024
788d4fb
Refactor rate limit headers to constants
kfcampbell Mar 29, 2024
dffadfb
Merge branch 'main' into rate-limit-playground
kfcampbell Apr 2, 2024
38739b6
Rename to cmd/example
kfcampbell Apr 2, 2024
669a06d
Shorten abstractions --> abs
kfcampbell Apr 2, 2024
1f3baae
Small refactor and comments rework on rate limit handler
kfcampbell Apr 2, 2024
eba9a00
Continue small polish refactors
kfcampbell Apr 2, 2024
e7b7337
Remove rate limit test example
kfcampbell Apr 2, 2024
62a488b
Sharpening comments, tests, error cases
kfcampbell Apr 2, 2024
93c3ce8
PR once-over small fixes
kfcampbell Apr 2, 2024
be77944
Set client timeout and base URL. Rework logs
kfcampbell Apr 2, 2024
6a998a9
Merge branch 'main' into rate-limit-playground
kfcampbell Apr 8, 2024
f5a3c3e
Merge branch 'main' into rate-limit-playground
kfcampbell Apr 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions cmd/constructor-playground/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"context"
"fmt"
"log"
"os"
"time"

abstractions "github.com/microsoft/kiota-abstractions-go"
"github.com/octokit/go-sdk/pkg"
)

func main() {
// as a consumer, how do i want to use the default API client and constructor?
// tenets:
// - minimal chaining
// - functional options
// - defaults include rate-limiting middleware and other sensible values (included without specification)
client, err := pkg.NewApiClient(
pkg.WithUserAgent("my-user-agent"),
pkg.WithRequestTimeout(5*time.Second),
pkg.WithRateLimitingMiddleware(),
pkg.WithBaseUrl("https://api.github.com"),
pkg.WithAuthorizationToken(os.Getenv("GITHUB_TOKEN")),
)

// equally valid:
//client, err := pkg.NewApiClient()
if err != nil {
log.Fatalf("error creating client: %v", err)
}

queryParams := &abstractions.DefaultQueryParameters{}
requestConfig := &abstractions.RequestConfiguration[abstractions.DefaultQueryParameters]{
QueryParameters: queryParams,
}
zen, err := client.Zen().Get(context.Background(), requestConfig)
if err != nil {
fmt.Printf("error getting Zen: %v\n", err)
return
}
fmt.Printf("%v\n", *zen)
}
68 changes: 68 additions & 0 deletions cmd/rate-limit-test/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main
kfcampbell marked this conversation as resolved.
Show resolved Hide resolved

import (
"context"
"log"
"os"

abstractions "github.com/microsoft/kiota-abstractions-go"
kiotaHttp "github.com/microsoft/kiota-http-go"
auth "github.com/octokit/go-sdk/pkg/authentication"
"golang.org/x/sync/errgroup"

"github.com/octokit/go-sdk/pkg/github"
"github.com/octokit/go-sdk/pkg/github/user"
"github.com/octokit/go-sdk/pkg/github/user/repos"
"github.com/octokit/go-sdk/pkg/handlers"
)

func main() {
rateLimitHandler := handlers.NewRateLimitHandler()
middlewares := kiotaHttp.GetDefaultMiddlewares()
middlewares = append(middlewares, rateLimitHandler)
netHttpClient := kiotaHttp.GetDefaultClient(middlewares...)

tokenProvider := auth.NewTokenProvider(
auth.WithAuthorizationToken(os.Getenv("GITHUB_TOKEN")),
auth.WithUserAgent("octokit/go-sdk.example-functions"),
)

adapter, err := kiotaHttp.NewNetHttpRequestAdapterWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient(tokenProvider, nil, nil, netHttpClient)
if err != nil {
log.Fatalf("Error creating request adapter: %v", err)
}
adapter.SetBaseUrl("http://api.github.localhost:1024")

client := github.NewApiClient(adapter)
errGroup := &errgroup.Group{}
for i := 0; i < 1500; i++ {
errGroup.Go(func() error {
viz := repos.ALL_GETVISIBILITYQUERYPARAMETERTYPE
queryParams := &user.ReposRequestBuilderGetQueryParameters{
Visibility: &viz,
}
requestConfig := &abstractions.RequestConfiguration[user.ReposRequestBuilderGetQueryParameters]{
QueryParameters: queryParams,
}
_, err := client.User().Repos().Get(context.Background(), requestConfig)
if err != nil {
log.Fatalf("error getting repositories: %v", err)
return err
}

// if len(repos) > 0 {
kfcampbell marked this conversation as resolved.
Show resolved Hide resolved
// log.Printf("Repositories:\n")
// for _, repo := range repos {
// log.Printf("%v\n", *repo.GetFullName())
// }
// }
return nil
})
}
if err := errGroup.Wait(); err != nil {
log.Fatalf("error from errgroup getting repositories: %v", err)
} else {
log.Printf("ran into no errors.")
}
// Output:
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/sync v0.6.0
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
4 changes: 2 additions & 2 deletions pkg/authentication/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"log"

abstractions "github.com/microsoft/kiota-abstractions-go"
http "github.com/microsoft/kiota-http-go"
kiotaHttp "github.com/microsoft/kiota-http-go"
auth "github.com/octokit/go-sdk/pkg/authentication"
"github.com/octokit/go-sdk/pkg/github"
"github.com/octokit/go-sdk/pkg/github/octocat"
Expand All @@ -20,7 +20,7 @@ func ExampleApiClient_Octocat() {
// auth.WithAuthorizationToken("ghp_your_token"),
auth.WithUserAgent("octokit/go-sdk.example-functions"),
)
adapter, err := http.NewNetHttpRequestAdapter(tokenProvider)
adapter, err := kiotaHttp.NewNetHttpRequestAdapter(tokenProvider)
if err != nil {
log.Fatalf("Error creating request adapter: %v", err)
}
Expand Down
119 changes: 119 additions & 0 deletions pkg/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package pkg

import (
"fmt"
"time"

kiotaHttp "github.com/microsoft/kiota-http-go"
auth "github.com/octokit/go-sdk/pkg/authentication"
"github.com/octokit/go-sdk/pkg/github"
"github.com/octokit/go-sdk/pkg/handlers"
)

func NewApiClient(optionFuncs ...ClientOptionFunc) (*Client, error) {
options := GetDefaultClientOptions()
for _, optionFunc := range optionFuncs {
optionFunc(options)
}

rateLimitHandler := handlers.NewRateLimitHandler()
middlewares := kiotaHttp.GetDefaultMiddlewares()
middlewares = append(middlewares, rateLimitHandler)
netHttpClient := kiotaHttp.GetDefaultClient(middlewares...)

tokenProviderOptions := []auth.TokenProviderOption{
auth.WithAPIVersion(options.APIVersion),
auth.WithUserAgent(options.UserAgent),
}
if options.Token != "" {
tokenProviderOptions = append(tokenProviderOptions, auth.WithAuthorizationToken(options.Token))
}

tokenProvider := auth.NewTokenProvider(tokenProviderOptions...)

adapter, err := kiotaHttp.NewNetHttpRequestAdapterWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient(tokenProvider, nil, nil, netHttpClient)
if err != nil {
return nil, fmt.Errorf("failed to create request adapter: %v", err)
}

client := github.NewApiClient(adapter)
sdkClient := &Client{
ApiClient: client,
}

return sdkClient, nil
}

// Client wraps github.ApiClient so that we may provide neater constructors and ease of use
type Client struct {
*github.ApiClient
}

// ClientOptions contains every setting we could possibly want to set for the token provider,
// the netHttpClient, the middleware, and the adapter. If we can possibly override it, it should
// be in this struct.
// In the constructor, when helper functions apply options, they'll be applied to this struct.
// Then later in the constructor when the chain of objects are put together, all configuration
// will be drawn from this (hydrated) struct.
type ClientOptions struct {
UserAgent string
APIVersion string
RequestTimeout time.Duration
Middleware []kiotaHttp.Middleware
BaseURL string
Token string
}

// GetDefaultClientOptions returns a new instance of ClientOptions with default values.
// This is used in the NewApiClient constructor before applying user-defined custom options.
func GetDefaultClientOptions() *ClientOptions {
return &ClientOptions{
UserAgent: "octokit/go-sdk",
APIVersion: "2022-11-28",
Middleware: kiotaHttp.GetDefaultMiddlewares(),
}
}

// ClientOptionFunc provides a functional pattern for client configuration
type ClientOptionFunc func(*ClientOptions)

// example of an auth token provider option
func WithUserAgent(userAgent string) ClientOptionFunc {
return func(c *ClientOptions) {
c.UserAgent = userAgent
}
}

// example of a netHttpClient option
func WithRequestTimeout(timeout time.Duration) ClientOptionFunc {
return func(c *ClientOptions) {
c.RequestTimeout = timeout
}
}

// example of a middleware option
func WithRateLimitingMiddleware() ClientOptionFunc {
return func(c *ClientOptions) {
rateLimitHandler := handlers.NewRateLimitHandler()
c.Middleware = append(c.Middleware, rateLimitHandler)
}
}

// example of an adapter option
func WithBaseUrl(baseURL string) ClientOptionFunc {
return func(c *ClientOptions) {
c.BaseURL = baseURL
}
}

func WithAuthorizationToken(token string) ClientOptionFunc {
return func(c *ClientOptions) {
c.Token = token
}
}

func WithAPIVersion(version string) ClientOptionFunc {
return func(c *ClientOptions) {
c.APIVersion = version
}
}
Loading
Loading