UNPKG

15.5 kBMarkdownView Raw
1# Thrift Client
2
3Thrift client library for NodeJS written in TypeScript.
4
5Supports communicating with Thrift services over HTTP or TCP.
6
7## Usage
8
9We're going to go through this step-by-step.
10
11* Install dependencies
12* Define our service
13* Run codegen on our Thrift IDL
14* Create a client
15* Make service calls with our client
16
17### Install
18
19All Thrift Server libraries define inter-dependencies as peer dependencies to avoid type collisions.
20
21```sh
22$ npm install --save-dev @creditkarma/thrift-typescript
23$ npm install --save @creditkarma/thrift-server-core
24$ npm install --save @creditkarma/thrift-client
25$ npm install --save request
26$ npm install --save @types/request
27```
28
29### Example Service
30
31```c
32service Calculator {
33 i32 add(1: i32 left, 2: i32 right)
34 i32 subtract(1: i32 left, 2: i32 right)
35}
36```
37
38### Codegen
39
40Requires @creditkarma/thrift-typescript >= v3.0.0
41
42Add a script to your package.json to codegen. The 'target' option is important to make thrift-typescript generate for this library instead of the Apache libraries.
43
44```json
45"scripts": {
46 "codegen": "thrift-typescript --target thrift-server --sourceDir thrift --outDir codegen"
47}
48```
49
50Then you can run codegen:
51
52```sh
53$ npm run codegen
54```
55
56### Creating an HTTP Client
57
58There are two ways to create HTTP clients with the public API.
59
60* Use the `createHttpClient` factory function.
61* Manually create your own `HttpConnection` object
62
63#### `createHttpClient`
64
65Using the `createHttpClient` function you pass in two arguments, the first is your Thrift service client class and the second is a map of options to configure the underlying HTTP connection.
66
67When creating a client using this method Thrift Client uses the [Request library](https://github.com/request/request) for making HTTP requests.
68
69```typescript
70import {
71 createHttpClient
72} from '@creditkaram/thrift-client'
73
74import { CoreOptions } from 'request'
75
76import { Calculator } from './codegen/calculator'
77
78// Create Thrift client
79const thriftClient: Calculator.Client<CoreOptions> = createHttpClient(Calculator.Client, {
80 serviceName: 'calculator-service',
81 hostName: 'localhost',
82 port: 8080,
83 requestOptions: {} // CoreOptions to pass to Request
84})
85```
86
87##### Options
88
89The available options are:
90
91* serviceName (optional): The name of your service. Used for logging.
92* hostName (required): The name of the host to connect to.
93* port (required): The port number to attach to on the host.
94* path (optional): The path on which the Thrift service is listening. Defaults to '/thrift'.
95* https (optional): Boolean value indicating whether to use https. Defaults to false.
96* transport (optional): Name of the Thrift transport type to use. Defaults to 'buffered'.
97* protocol (optional): Name of the Thrift protocol type to use. Defaults to 'binary'.
98* requestOptions (optional): Options to pass to the underlying [Request library](https://github.com/request/request#requestoptions-callback). Defaults to {}.
99* register (optional): A list of filters to apply to your client. More on this later.
100
101Currently `@creditkarma/thrift-server-core"` only supports buffered transport and binary or compact protocols.
102
103```typescript
104type TransportType = 'buffered'
105```
106
107The possible protocol types are:
108
109```typescript
110type ProtocolType = 'binary' | 'compact'
111```
112
113#### Manual Creation
114
115Manually creating your Thrift client allows you to choose the use of another HTTP client library or to reuse a previously created instance of Request.
116
117```typescript
118import {
119 RequestInstance,
120 HttpConnection,
121 IHttpConnectionOptions,
122} from '@creditkaram/thrift-client'
123import * as request from 'request'
124import { CoreOptions } from 'request'
125
126import { Calculator } from './codegen/calculator'
127
128const clientConfig: IHttpConnectionOptions = {
129 hostName: 'localhost',
130 port: 3000,
131 path: '/',
132 transport: 'buffered',
133 protocol: 'binary',
134}
135
136// Create Thrift client
137const requestClient: RequestInstance = request.defaults({})
138
139const connection: HttpConnection =
140 new HttpConnection(requestClient, clientConfig)
141
142const thriftClient: Calculator.Client<CoreOptions> = new Calculator.Client(connection)
143```
144
145Here `HttpConnection` is a class that extends the `ThriftConnection` abstract class. You could create custom connections, for instance TCP, by extending the same class.
146
147Also of note here is that the type `IHttpConnectionOptions` does not accept the `requestOptions` parameter. Options to `Request` here would be passed directly to the call to `request.defaults({})`.
148
149### Creating a TCP Client
150
151Creating a TCP client is much like creating an HTTP client.
152
153* Use the `createTcpClient` factory function.
154* Manually create your own `TcpConnection` object
155
156*Note: Our TcpConnection object uses an underlying connection pool instead of all client requests reusing the same connection. This pool is configurable. We use [GenericPool](https://github.com/coopernurse/node-pool) for managing our connection pool. Pool options are passed directly through to GenericPool.*
157
158#### `createTcpClient`
159
160Using the `createTcpClient` function you pass in two arguments, the first is your Thrift service client class and the second is a map of options to configure the underlying TCP connection.
161
162```typescript
163import {
164 createTcpClient
165} from '@creditkaram/thrift-client'
166
167import { Calculator } from './codegen/calculator'
168
169// Create Thrift client
170const thriftClient: Calculator.Client<CoreOptions> = createTcpClient(Calculator.Client, {
171 serviceName: 'calculator-service',
172 hostName: 'localhost',
173 port: 8080,
174})
175```
176
177##### Options
178
179The available options are:
180
181* serviceName (optional): The name of your service. Used for logging.
182* hostName (required): The name of the host to connect to.
183* port (required): The port number to attach to on the host.
184* timeout (optional): Connection timeout in milliseconds. Defaults to 5000.
185* transport (optional): Name of the Thrift transport type to use. Defaults to 'buffered'.
186* protocol (optional): Name of the Thrift protocol type to use. Defaults to 'binary'.
187* pool (optional): Options to configure the underlying connection pool.
188* register (optional): A list of filters to apply to your client. More on this later.
189
190#### Manual Creation
191
192```typescript
193import {
194 TcpConnection,
195} from '@creditkaram/thrift-client'
196
197import { Calculator } from './codegen/calculator'
198
199const connection: TcpConnection = new TcpConnection({
200 hostName: 'localhost',
201 port: 3000,
202 transport: 'buffered',
203 protocol: 'binary',
204})
205
206const thriftClient: Calculator.Client<CoreOptions> = new Calculator.Client(connection)
207```
208
209Here `TcpConnection` is a class that extends the `ThriftConnection` abstract class. You could create custom connections, for instance TCP, by extending the same class.
210
211### Making Service Calls with our Client
212
213However we chose to make our client, we use them in the same way.
214
215Notice the optional context parameter. All service client methods can take an optional context parameter. This context refers to the request options for Request library (CoreOptions). These options will be deep merged with any default options (passed in on instantiation) before sending a service request. This context can be used to do useful things like tracing or authentication. Usually this will be used for changing headers on a per-request basis.
216
217Related to context you will notice that our Thrift service client is a generic `Calculator.Client<ThriftContext<CoreOptions>>`. This type parameter refers to the type of the context, here the `ThriftContext<CoreOptions>` which extends the options interface from the Request library.
218
219```typescript
220import {
221 createHttpClient
222} from '@creditkaram/thrift-client'
223
224import { CoreOptions } from 'request'
225import * as express from 'express'
226
227import { Calculator } from './codegen/calculator'
228
229const serverConfig = {
230 hostName: 'localhost',
231 port: 8080,
232}
233
234// Create Thrift client
235const thriftClient: Calculator.Client<ThriftContext<CoreOptions>> = createHttpClient(Calculator.Client, {
236 hostName: 'localhost',
237 port: 8080,
238 requestOptions: {} // CoreOptions to pass to Request
239})
240
241// This receives a query like "http://localhost:8080/add?left=5&right=3"
242app.get('/add', (req: express.Request, res: express.Response): void => {
243 // Request contexts allow you to do tracing and auth
244 const context: CoreOptions = {
245 headers: {
246 'x-trace-id': 'my-trace-id'
247 }
248 }
249
250 // Client methods return a Promise of the expected result
251 thriftClient.add(req.query.left, req.query.right, context).then((result: number) => {
252 res.send(result)
253 }, (err: any) => {
254 res.status(500).send(err)
255 })
256})
257
258app.listen(serverConfig.port, () => {
259 console.log(`Web server listening at http://${serverConfig.hostName}:${serverConfig.port}`)
260})
261```
262
263### Client Filters
264
265Sometimes you'll want to universally filter or modify requests and/or responses. This is done with filters. If you've used many server libraries you are probably used to the idea of plugins or middleware.
266
267A filter is an object that consists of a handler function and an optional list of client method names to apply the filter to.
268
269Filters are applied in the order in which they are registered.
270
271```typescript
272interface IRequestResponse {
273 statusCode: number
274 headers: IRequestHeaders
275 body: Buffer
276}
277
278type NextFunction<Options> =
279 (data?: Buffer, options?: Options) => Promise<IRequestResponse>
280
281interface IThriftClientFilterConfig<Options> {
282 methods?: Array<string>
283 handler(request: IThriftRequest<Options>, next: NextFunction<Options>): Promise<IRequestResponse>
284}
285```
286
287The `handler` function receives as its arguments the `IThriftRequest` object and the next `RequestHandler` in the chain. When you are done applying your filter you `return` the call to `next`. When calling `next` you can optionally pass along a modified Thrift data `Buffer` or new options to apply to the request. `Options` is almost always going to be `CoreOptions` for the underlying request library.
288
289#### IThriftRequest
290
291The `IThriftRequest` object wraps the outgoing data with some useful metadata about the current request.
292
293```typescript
294export interface IThriftRequest<Context> {
295 data: Buffer
296 methodName: string
297 uri: string
298 context: Context
299}
300```
301
302The `data` attribute is the outgoing Thrift payload. The `methodName` is the name of the Thrift service method being called.
303
304The other interesting one is `context`. The context is the data passed through in the service method call.
305
306If we look back at our previous client example we find this:
307
308```typescript
309// This receives a query like "http://localhost:8080/add?left=5&right=3"
310app.get('/add', (req: express.Request, res: express.Response): void => {
311 // Request contexts allow you to do tracing and auth
312 const context: CoreOptions = {
313 headers: {
314 'x-trace-id': 'my-trace-id'
315 }
316 }
317
318 // Client methods return a Promise of the expected result
319 thriftClient.add(req.query.left, req.query.right, context).then((result: number) => {
320 res.send(result)
321 }, (err: any) => {
322 res.status(500).send(err)
323 })
324})
325```
326
327Where the context passed through is an instance of `CoreOptions`. These options are passed through to modify the outgoing request. Any filter in the filter chain can modify this data.
328
329To modify this context one needs to pass the updated context to the `next` function.
330
331```typescript
332return next(request.data, updatedContext)
333```
334
335#### Applying Filters to Outgoing Requests
336
337Something you may want to do with middlware is to apply some common HTTP headers to every outgoing request from your service. Maybe there is a token your service should attach to every outgoing request.
338
339You could do something like this:
340
341```typescript
342import {
343 createHttpClient,
344 IThriftRequest,
345} from '@creditkaram/thrift-client'
346
347import { Calculator } from './codegen/calculator'
348
349const thriftClient: Calculator.Client = createHttpClient(Calculator.Client, {
350 hostName: 'localhost',
351 port: 8080,
352 register: [ {
353 handler(request: IThriftRequest<CoreOptions>, next: NextFunction<CoreOptions>): Promise<IRequestResponse> {
354 return next(request.data, {
355 headers: {
356 'x-fake-token': 'fake-token',
357 },
358 })
359 },
360 } ]
361})
362```
363
364This sends data along unaltered, but adds a header `x-fake-token` to the outgoing request. When you send along options, the options are deep merged with any previous options that were applied.
365
366#### Applying Filters to Incoming Responses
367
368To apply filters to the response you would call `.then` on the `next` function. This would allow you to inspect or modify the response before allowing it to proceed up the chain.
369
370```typescript
371import {
372 createHttpClient,
373 IThriftRequest
374} from '@creditkaram/thrift-client'
375
376import { Calculator } from './codegen/calculator'
377
378const thriftClient: Calculator.Client = createHttpClient(Calculator.Client, {
379 hostName: 'localhost',
380 port: 8080,
381 register: [ {
382 handler(request: IThriftRequest<CoreOptions>, next: NextFunction<CoreOptions>): Promise<IRequestResponse> {
383 return next().then((res: IRequestResponse) => {
384 if (validateResponse(res.body)) {
385 return res
386 } else {
387 throw new Error('Invalid data returned')
388 }
389 })
390 },
391 } ]
392})
393```
394
395#### Adding Filters to HttpConnection Object
396
397When you're not using `createHttpClient` you can add filters directly to the connection instance.
398
399```typescript
400// Create thrift client
401const requestClient: RequestInstance = request.defaults({})
402
403const connection: HttpConnection =
404 new HttpConnection(requestClient, clientConfig)
405
406connection.register({
407 handler(request: IThriftRequest<CoreOptions>, next: NextFunction<CoreOptions>): Promise<IRequestResponse> {
408 return next(request.data, {
409 headers: {
410 'x-fake-token': 'fake-token',
411 },
412 })
413 },
414})
415
416const thriftClient: Calculator.Client = new Calculator.Client(connection)
417```
418
419The optional `register` option takes an array of filters to apply. Unsurprisingly they are applied in the order you pass them in.
420
421#### Available Filters
422
423These client filters are maintained as part of this repository:
424
425* [ThriftClientContextFilter](https://github.com/creditkarma/thrift-server/tree/master/packages/thrift-client-context-filter) - This is useful for TCP clients. It is a filter for appending Thrift objects onto an existing Thrift payload.
426* [ThriftClientTTwitterFilter](https://github.com/creditkarma/thrift-server/tree/master/packages/thrift-client-ttwitter-filter) - This is for appending a TTwitter context onto an existing Thrift payload. This is for compatibility with Twitter's [Finagle](https://twitter.github.io/finagle/) framework.
427* [ThriftClientZipkinFilter](https://github.com/creditkarma/thrift-server/tree/master/packages/thrift-client-zipkin-filter) - This is for supporting distributed tracing with [Zipkin](https://github.com/openzipkin/zipkin-js).
428
429## Contributing
430
431For more information about contributing new features and bug fixes, see our [Contribution Guidelines](../../CONTRIBUTING.md).
432External contributors must sign Contributor License Agreement (CLA)
433
434## License
435
436This project is licensed under [Apache License Version 2.0](./LICENSE)