Skip to content

Commit dff29d1

Browse files
committed
Merge branch 'em/deploy2' of https://github.com/oslabs-beta/GraphQL-Gate into em/deploy2
2 parents 18ca3ff + 52ce7ec commit dff29d1

File tree

1 file changed

+77
-10
lines changed

1 file changed

+77
-10
lines changed

README.md

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
- [Getting Started](#getting-started)
1414
- [Configuration](#configuration)
15+
- [Notes on Lists](#lists)
1516
- [How It Works](#how-it-works)
17+
- [Response](#response)
18+
- [Error Handling](#error-handling)
1619
- [Future Development](#future-development)
1720
- [Contributions](#contributions)
1821
- [Developers](#developers)
@@ -69,7 +72,7 @@ app.use(
6972
- `options: RedisOptions` | [ioredis configuration options](https://github.com/luin/ioredis) | defaults to standard ioredis connection options (`localhost:6379`)
7073
- `keyExpiry: number` (ms) | custom expiry of keys in redis cache | defaults to 24 hours
7174

72-
- `typeWeights: TypeWeightObject`
75+
- <a name="typeWeights"></a>`typeWeights: TypeWeightObject`
7376

7477
- `mutation: number` | assigned weight to mutations | defaults to 10
7578
- `query: number` | assigned weight of a query | defaults to 1
@@ -78,15 +81,15 @@ app.use(
7881

7982
- `depthLimit: number` | throttle queies by the depth of the nested stucture | defaults to `Infinity` (ie. no limit)
8083
- `enforceBoundedLists: boolean` | if true, an error will be thrown if any lists types are not bound by slicing arguments [`first`, `last`, `limit`] or directives | defaults to `false`
81-
- `dark: boolean` | if true, the package will calculate complexity, depth and tokens but not throttle any queries. Use this to dark launch the package and monitor what would happen if rate limiting was added to yaur application
84+
- `dark: boolean` | if true, the package will calculate complexity, depth and tokens but not throttle any queries. Use this to dark launch the package and monitor the rate limiter's impact without limiting user requests.
8285

8386
All configuration options
8487

8588
```javascript
8689
expressGraphQLRateLimiter(schemaObject, {
8790
rateLimiter: {
88-
type: 'TOKEN_BUCKET', // rate-limiter selection
89-
refillRate: 10,
91+
type: 'SLIDING_WINDOW_LOG', // rate-limiter selection
92+
windowSize: 6000, // 6 seconds
9093
capacity: 100,
9194
},
9295
redis: {
@@ -108,17 +111,81 @@ app.use(
108111
});
109112
```
110113

114+
## <a name="lists"></a> Notes on Lists
115+
116+
For queries that return a list, the complexity can be determined by providing a slicing argument to the query (`first`, `last`, `limit`), or using a schema directive.
117+
118+
1. Slicing arguments: lists must be bounded by one integer slicing argument in order to calculate the complexity for the field. This package supports the slicing arguments `first`, `last` and `limit`. The complexity of the list will be the value passed as the argument to the field.
119+
120+
2. Directives: To use directives, `@listCost` must be defined in your schema with `directive @listCost(cost: Int!) on FIELD_DEFINITION`. Then, on any unbounded list field, add `@listCost(cost: <Int>)` where `<Int>` is the complexity you want applied to the list when queried.
121+
122+
(Note: Slicing arguments are preferred! `@listCost` is in place for any reason slicing arguments cannot be used.)
123+
111124
## <a name="how-it-works"></a> How It Works
112125

113-
how are things weighted examples
126+
Requests are rate-limited based on the IP address associated with the request.
127+
128+
On server start, the GraphQL (GQL) schema is parsed to build an object that maps GQL types/fields to their corresponding weights. Type weights can be provided during <a href="typeWeights">initial configuration</a>. When a request is received, this object is used to cross reference the fields queried by the user and compute the complexity of each field. The total complexity of the request is the sum of these values.
129+
130+
Complexity is determined, statically (before any resolvers are called) to estimate the upper bound of the response size - a proxy for the work done by the server to build the response. The total complexity is then used to allow/block the request based on popular rate-limiting algorithms.
131+
132+
Requests for each user are processed sequentially by the rate limiter.
133+
134+
Example (with default weights):
135+
136+
```graphql
137+
query { # 1 query
138+
hero (episode: EMPIRE) { # 1 object
139+
name # 0 scalar
140+
id # 0 scalar
141+
friends (first: 3) { # 3 objects
142+
name # 0 scalar
143+
id # 0 scalar
144+
}
145+
}
146+
reviews (episode: EMPIRE, limit: 5) { # 5 objects
147+
stars # 0 scalar
148+
commentary # 0 scalar
149+
}
150+
} # total complexity of 10
151+
```
152+
153+
## <a name="response"></a> Response
154+
155+
1. <b>Blocked Requests</b>: blocked requests recieve a response with,
156+
157+
- status of `429` for `Too Many Requests`
158+
- `Retry-After` header with a value of the time to wait in seconds before the request would be approved (`Infinity` if the complexity is greater than rate-limiting capacity).
159+
- A JSON response with the `tokens` available, `complexity` of the query, `depth` of the query, `success` of the query set to `false`, and the UNIX `timestamp` of the request
160+
161+
2. <b>Successful Requests</b>: successful requests are passed onto the next function in the middleware chain with the following properties saved to `res.locals`
162+
163+
```javascript
164+
{
165+
graphglGate: {
166+
success: boolean, // true when successful
167+
tokens: number, // tokens available after request
168+
compexity: number, // complexity of the query
169+
depth: number, // depth of the query
170+
timestamp: number, // ms
171+
}
172+
}
173+
```
174+
175+
## <a name="error-handling"></a> Error Handling
176+
177+
- Incoming queries are validated against the GraphQL schema. If the query is invalid, a response with status code `400` is returned along with an array of GraphQL Errors that were found.
178+
- To avoid disrupting server activity, errors thrown during the analysis and rate-limiting of the query are logged and the request is passed onto the next middleware function in the chain.
114179

115180
## <a name="future-development"></a> Future Development
116181

117-
- configure rate-limiting cache with other caching libraries
118-
- resolve complexity analysis for queries
119-
- leaky bucket rate-limiting algorithm
120-
- experimint with performance improvments
121-
- caching optimizations
182+
- Ability to use this package with other caching technologies or libraries
183+
- Implement "resolve complexity analysis" for queries
184+
- Implement leaky bucket algorithm for rate-limiting
185+
- Experiment with performance improvements
186+
- caching optimization
187+
- Ensure connection pagination conventions can be accuratly acconuted for in complexity analysis
188+
- Ability to use middleware with other server frameworks
122189

123190
## <a name="contributions"></a> Contributions
124191

0 commit comments

Comments
 (0)