go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/examples/appengine/quotabeta/rpc/rpc.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package rpc implements a rate-limited RPC service. 16 package rpc 17 18 import ( 19 "context" 20 21 "google.golang.org/protobuf/runtime/protoiface" 22 23 "google.golang.org/grpc/codes" 24 "google.golang.org/protobuf/types/known/emptypb" 25 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/common/logging" 28 "go.chromium.org/luci/grpc/appstatus" 29 "go.chromium.org/luci/server/auth" 30 "go.chromium.org/luci/server/quotabeta" 31 32 pb "go.chromium.org/luci/examples/appengine/quotabeta/proto" 33 ) 34 35 // Demo implements pb.DemoServer. Requires a quotaconfig.Interface in the 36 // context for all method calls. 37 type Demo struct { 38 } 39 40 // Ensure Demo implements pb.DemoServer at compile-time. 41 var _ pb.DemoServer = &Demo{} 42 43 // GlobalRateLimit is globally limited to one request every 60 seconds. This 44 // quota can be reset at any time by calling GlobalQuotaReset. On success, 45 // returns an *emptypb.Empty, and on failure returns a codes.ResourceExhausted 46 // gRPC error. 47 func (*Demo) GlobalRateLimit(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 48 // global-rate-limit has a maximum of 60 resources. When below 60, 49 // automatically replenishes one resource per second. By debiting 50 // 60 resources on every call, enforce a rate limit of one request 51 // every 60 seconds. 52 updates := map[string]int64{ 53 "global-rate-limit": -60, 54 } 55 switch err := quota.UpdateQuota(ctx, updates, nil); err { 56 case nil: 57 return &emptypb.Empty{}, nil 58 case quota.ErrInsufficientQuota: 59 return nil, appstatus.Errorf(codes.ResourceExhausted, "global rate limit exceeded") 60 default: 61 return nil, errors.Annotate(err, "quota.UpdateQuota").Err() 62 } 63 } 64 65 // GlobalQuotaReset resets quota for calling GlobalRateLimit. Always succeeds, 66 // returning an *emptypb.Empty. 67 func (*Demo) GlobalQuotaReset(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 68 // global-rate-limit has a maximum of 60 resources. Credit all 60. 69 updates := map[string]int64{ 70 "global-rate-limit": 60, 71 } 72 switch err := quota.UpdateQuota(ctx, updates, nil); err { 73 case nil: 74 return &emptypb.Empty{}, nil 75 default: 76 return nil, errors.Annotate(err, "quota.UpdateQuota").Err() 77 } 78 } 79 80 // PerUserRateLimit is limited to two requests every 60 seconds for a given user. 81 // This quota can be reset at any time by calling PerUserQuotaReset. On success, 82 // returns an *emptypb.Empty, and on failure returns a codes.ResourceExhausted 83 // gRPC error. 84 func (*Demo) PerUserRateLimit(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 85 // per-user-rate-limit/${user} has a maximum of 60 resources per user. 86 // When below 60, automatically replenishes one resource per second. By 87 // debiting 30 resources on every call, enforce a rate limit of two requests 88 // every 60 seconds. 89 updates := map[string]int64{ 90 "per-user-rate-limit/${user}": -30, 91 } 92 opts := "a.Options{ 93 User: string(auth.CurrentIdentity(ctx)), 94 } 95 switch err := quota.UpdateQuota(ctx, updates, opts); err { 96 case nil: 97 return &emptypb.Empty{}, nil 98 case quota.ErrInsufficientQuota: 99 return nil, appstatus.Errorf(codes.ResourceExhausted, "per-user rate limit exceeded") 100 default: 101 return nil, errors.Annotate(err, "quota.UpdateQuota").Err() 102 } 103 } 104 105 // PerUserQuotaReset resets the caller's own quota for calling PerUserRateLimit. 106 // Always succeeds, returning an *emptypb.Empty. 107 func (*Demo) PerUserQuotaReset(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 108 // per-user-rate-limit has a maximum of 60 resources per user. Credit all 60. 109 updates := map[string]int64{ 110 "per-user-rate-limit/${user}": 60, 111 } 112 opts := "a.Options{ 113 User: string(auth.CurrentIdentity(ctx)), 114 } 115 switch err := quota.UpdateQuota(ctx, updates, opts); err { 116 case nil: 117 return &emptypb.Empty{}, nil 118 default: 119 return nil, errors.Annotate(err, "quota.UpdateQuota").Err() 120 } 121 } 122 123 // New returns a new pb.DemoServer. 124 func New() pb.DemoServer { 125 return &pb.DecoratedDemo{ 126 // Prelude logs the details of every request. 127 Prelude: func(ctx context.Context, methodName string, _ protoiface.MessageV1) (context.Context, error) { 128 logging.Debugf(ctx, "%q called %q", auth.CurrentIdentity(ctx), methodName) 129 return ctx, nil 130 }, 131 132 Service: &Demo{}, 133 134 // Postlude logs non-GRPC errors, and returns them as gRPC internal errors. 135 Postlude: func(ctx context.Context, _ string, _ protoiface.MessageV1, err error) error { 136 return appstatus.GRPCifyAndLog(ctx, err) 137 }, 138 } 139 }