Skip to content

Commit

Permalink
Merge pull request #2 from HatsuneMiku3939/feature/initial-implementa…
Browse files Browse the repository at this point in the history
…tion

feature: initial implementation
  • Loading branch information
HatsuneMiku3939 committed Apr 6, 2022
2 parents 3dff36f + e5b23cd commit 8ad294c
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 0 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,46 @@
# rltransport
The RoundTripper which limits the number of concurrent requests.

## examples

```golang
package main

import (
"fmt"
"net/http"
"time"

"github.com/HatsuneMiku3939/rltransport"

"golang.org/x/time/rate"
)

const (
// TestBurstSize is the default value for the rate limiter's burst size.
TestBurstSize = 10
// TestRefillRate is the default value for the rate limiter's refill rate.
TestRefillRate = 1.0
// TestURL is the URL to use for testing.
TestHost = "http://localhost:8080/"
)

func main() {
// Create a "tocket bucket" limiter with a burst size of 10 and a refill rate of 1.0/sec.
limiter := rate.NewLimiter(TestRefillRate, TestBurstSize)

// Create a new http.Client with the limiter.
client := &http.Client{
Transport: &rltransport.RoundTripper{
Limiter: limiter,
},
}

// Make a request to the server.
// First 10 requests will be sented immadiately, after that it will be sented by 1.0 req/sec.
for i := 0; i < 20; i++ {
res, _ := client.Get(TestHost)
fmt.Printf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), res.Status)
}
}
```
10 changes: 10 additions & 0 deletions example/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/HatsuneMiku3939/rltransport/example

go 1.17

replace github.com/HatsuneMiku3939/rltransport => ../

require (
github.com/HatsuneMiku3939/rltransport v0.0.0-00010101000000-000000000000
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
)
2 changes: 2 additions & 0 deletions example/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
61 changes: 61 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"fmt"
"net/http"
"time"

"github.com/HatsuneMiku3939/rltransport"

"golang.org/x/time/rate"
)

const (
// TestBurstSize is the default value for the rate limiter's burst size.
TestBurstSize = 10
// TestRefillRate is the default value for the rate limiter's refill rate.
TestRefillRate = 1.0
// TestURL is the URL to use for testing.
TestHost = "http://localhost:8080/"
)

func main() {
// Create a "tocket bucket" limiter with a burst size of 10 and a refill rate of 1.0/sec.
limiter := rate.NewLimiter(TestRefillRate, TestBurstSize)

// Create a new http.Client with the limiter.
client := &http.Client{
Transport: &rltransport.RoundTripper{
Limiter: limiter,
},
}

// Make a request to the server.
// First 10 requests will be sented immadiately, after that it will be sented by 1.0 req/sec.
for i := 0; i < 20; i++ {
res, _ := client.Get(TestHost)
fmt.Printf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), res.Status)
}

// Will be printed:
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:09] 200 OK
// [2022-04-06 20:11:10] 200 OK ## <-- First 10 requests will be sented immadiately.
// [2022-04-06 20:11:11] 200 OK
// [2022-04-06 20:11:12] 200 OK
// [2022-04-06 20:11:13] 200 OK
// [2022-04-06 20:11:14] 200 OK
// [2022-04-06 20:11:15] 200 OK
// [2022-04-06 20:11:16] 200 OK
// [2022-04-06 20:11:17] 200 OK
// [2022-04-06 20:11:18] 200 OK
// [2022-04-06 20:11:19] 200 OK
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/HatsuneMiku3939/rltransport

go 1.17

require golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
19 changes: 19 additions & 0 deletions limiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package rltransport

import (
"context"
)

// Limiter is an interface that rate limiter must implement.
type Limiter interface {
// Wait blocks until the request can be sent.
Wait(ctx context.Context) error
}

// unlimitedLimiter is a limiter that always allows requests to be sent.
type unlimitedLimiter struct{}

// Wait always success.
func (l *unlimitedLimiter) Wait(ctx context.Context) error {
return nil
}
48 changes: 48 additions & 0 deletions roundtripper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package rltransport

import (
"net/http"
"sync"
)

// RoundTripper implements the http.RoundTripper interface.
type RoundTripper struct {
// once ensures that the logic to initialize the default client runs at
// most once, in a single thread.
once sync.Once

// Limiter is used to rate limit the number of requests that can be made
// to the underlying client.
Limiter Limiter

// Transport is the underlying RoundTripper that will be used to make
// the actual HTTP requests.
Transport http.RoundTripper
}

// RoundTrip satisfies the http.RoundTripper interface.
func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Ensure that the Transport is initialized.
rt.once.Do(rt.init)

// Wait for the rate limiter within context of the request (if limiter is given).
if err := rt.Limiter.Wait(req.Context()); err != nil {
return nil, err
}

// Execute the request.
resp, err := rt.Transport.RoundTrip(req)

return resp, err
}

// init initializes the underlying transport.
func (rt *RoundTripper) init() {
if rt.Transport == nil {
rt.Transport = http.DefaultTransport
}

if rt.Limiter == nil {
rt.Limiter = &unlimitedLimiter{}
}
}

0 comments on commit 8ad294c

Please sign in to comment.