Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- [Loading Configuration](#loading-configuration)
- [Request Fields](#request-fields)
- [Statistics](#statistics)
- [HTTP Port](#http-port)
- [/json endpoint](#json-endpoint)
- [Debug Port](#debug-port)
- [Local Cache](#local-cache)
- [Redis](#redis)
Expand Down Expand Up @@ -360,6 +362,29 @@ ratelimit.service.rate_limit.messaging.message_type_marketing.to_number.over_lim
ratelimit.service.rate_limit.messaging.message_type_marketing.to_number.total_hits: 0
```

# HTTP Port

The ratelimit service listens to HTTP 1.1 (by default on port 8080) with two endpoints:
1. /healthcheck → return a 200 if this service is healthy
1. /json → HTTP 1.1 endpoint for interacting with ratelimit service

## /json endpoint

Takes an HTTP POST with a JSON body of the form e.g.
```json
{
"domain": "dummy",
"descriptors": [
{"entries": [
{"key": "one_per_day",
"value": "something"}
]}
]
}
```
The service will return an http 200 if this request is allowed (if no ratelimits exceeded) or 429 if one or more
ratelimits were exceeded. Endpoint does not currently return detailed information on which limits were exceeded.

# Debug Port

The debug port can be used to interact with the running process.
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ services:
- .:/go/src/github.com/envoyproxy/ratelimit
- binary:/usr/local/bin/

ratelimit-client-build:
image: golang:1.14-alpine
working_dir: /go/src/github.com/envoyproxy/ratelimit
command: go build -o /usr/local/bin/ratelimit_client ./src/client_cmd/main.go
volumes:
- .:/go/src/github.com/envoyproxy/ratelimit
- binary:/usr/local/bin/

ratelimit:
image: alpine:3.6
command: /usr/local/bin/ratelimit
Expand All @@ -29,6 +37,7 @@ services:
depends_on:
- redis
- ratelimit-build
- ratelimit-client-build
networks:
- ratelimit-network
volumes:
Expand Down
2 changes: 2 additions & 0 deletions src/server/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2"
"net/http"

"github.com/lyft/goruntime/loader"
Expand All @@ -25,6 +26,7 @@ type Server interface {
* Add an HTTP endpoint to the local debug port.
*/
AddDebugHttpEndpoint(path string, help string, handler http.HandlerFunc)
AddJsonHandler(pb.RateLimitServiceServer)

/**
* Returns the embedded gRPC server to be used for registering gRPC endpoints.
Expand Down
33 changes: 33 additions & 0 deletions src/server/server_impl.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"encoding/json"
"expvar"
"fmt"
"io"
Expand All @@ -17,6 +18,7 @@ import (
"net"

"github.com/coocood/freecache"
pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2"
"github.com/envoyproxy/ratelimit/src/settings"
"github.com/gorilla/mux"
reuseport "github.com/kavu/go_reuseport"
Expand Down Expand Up @@ -52,6 +54,37 @@ func (server *server) AddDebugHttpEndpoint(path string, help string, handler htt
server.debugListener.endpoints[path] = help
}

// add an http/1 handler at the /json endpoint which allows this ratelimit service to work with
// clients that cannot use the gRPC interface (e.g. lua)
// example usage from cURL with domain "dummy" and descriptor "perday":
// echo '{"domain": "dummy", "descriptors": [{"entries": [{"key": "perday"}]}]}' | curl -vvvXPOST --data @/dev/stdin localhost:8080/json
func (server *server) AddJsonHandler(svc pb.RateLimitServiceServer) {
handler := func(writer http.ResponseWriter, request *http.Request) {
var req pb.RateLimitRequest

if err := json.NewDecoder(request.Body).Decode(&req); err != nil {
logger.Warnf("error: %s", err.Error())
http.Error(writer, err.Error(), http.StatusBadRequest)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case for this path.

return
}

resp, err := svc.ShouldRateLimit(nil, &req)
if err != nil {
logger.Warnf("error: %s", err.Error())
http.Error(writer, err.Error(), http.StatusBadRequest)
return
}
logger.Debugf("resp:%s", resp)
if resp.OverallCode == pb.RateLimitResponse_OVER_LIMIT {
http.Error(writer, "over limit", http.StatusTooManyRequests)
} else if resp.OverallCode == pb.RateLimitResponse_UNKNOWN {
http.Error(writer, "unknown", http.StatusInternalServerError)
}
Comment on lines +80 to +82
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this happen? Can this happen?

Copy link
Copy Markdown
Contributor Author

@dblackdblack dblackdblack May 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this check for completeness with the protobuf definition. I have never personally seen it. However if it did occur, we would not want to return a 200 OK to the user (which is what would happen absent this check).


}
server.router.HandleFunc("/json", handler)
}

func (server *server) GrpcServer() *grpc.Server {
return server.grpcServer
}
Expand Down
2 changes: 2 additions & 0 deletions src/service_cmd/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func (runner *Runner) Run() {
io.WriteString(writer, service.GetCurrentConfig().Dump())
})

srv.AddJsonHandler(service)

// Ratelimit is compatible with two proto definitions
// 1. data-plane-api rls.proto: https://github.com/envoyproxy/data-plane-api/blob/master/envoy/service/ratelimit/v2/rls.proto
pb.RegisterRateLimitServiceServer(srv.GrpcServer(), service)
Expand Down
27 changes: 27 additions & 0 deletions test/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package integration_test

import (
"bytes"
"fmt"
"math/rand"
"net/http"
"os"
"strconv"
"testing"
Expand Down Expand Up @@ -344,6 +346,7 @@ func TestBasicConfigLegacy(t *testing.T) {

assert := assert.New(t)
conn, err := grpc.Dial("localhost:8083", grpc.WithInsecure())

assert.NoError(err)
defer conn.Close()
c := pb_legacy.NewRateLimitServiceClient(conn)
Expand All @@ -358,6 +361,30 @@ func TestBasicConfigLegacy(t *testing.T) {
response)
assert.NoError(err)

json_body := []byte(`{
"domain": "basic",
"descriptors": [
{
"entries": [
{
"key": "one_per_minute"
}
]
}
]
}`)
http_resp, _ := http.Post("http://localhost:8082/json", "application/json", bytes.NewBuffer(json_body))
assert.Equal(http_resp.StatusCode, 200)
http_resp.Body.Close()

http_resp, _ = http.Post("http://localhost:8082/json", "application/json", bytes.NewBuffer(json_body))
assert.Equal(http_resp.StatusCode, 429)
http_resp.Body.Close()

invalid_json := []byte(`{"unclosed quote: []}`)
http_resp, _ = http.Post("http://localhost:8082/json", "application/json", bytes.NewBuffer(invalid_json))
assert.Equal(http_resp.StatusCode, 400)

response, err = c.ShouldRateLimit(
context.Background(),
common.NewRateLimitRequestLegacy("basic_legacy", [][][2]string{{{"key1", "foo"}}}, 1))
Expand Down
5 changes: 5 additions & 0 deletions test/integration/runtime/current/ratelimit/config/basic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ descriptors:
rate_limit:
unit: second
requests_per_unit: 50

- key: one_per_minute
rate_limit:
unit: minute
requests_per_unit: 1