grpc-server-js
Advanced tools
Comparing version 0.0.4 to 0.0.5
@@ -36,2 +36,7 @@ 'use strict'; | ||
class IdentityHandler extends CompressionHandler { | ||
constructor () { | ||
super(); | ||
this.name = 'identity'; | ||
} | ||
compressMessage (message) { // eslint-disable-line class-methods-use-this | ||
@@ -60,2 +65,7 @@ throw new Error('Identity encoding does not support compression'); | ||
class GzipHandler extends CompressionHandler { | ||
constructor () { | ||
super(); | ||
this.name = 'gzip'; | ||
} | ||
compressMessage (message) { // eslint-disable-line class-methods-use-this | ||
@@ -88,2 +98,7 @@ return new Promise((resolve, reject) => { | ||
class DeflateHandler extends CompressionHandler { | ||
constructor () { | ||
super(); | ||
this.name = 'deflate'; | ||
} | ||
compressMessage (message) { // eslint-disable-line class-methods-use-this | ||
@@ -115,32 +130,80 @@ return new Promise((resolve, reject) => { | ||
function getCompressionHandler (compressionName) { | ||
if (typeof compressionName !== 'string') { | ||
throw new Error('Compression method must be a string'); | ||
// This class tracks all compression methods supported by a server. | ||
// TODO: Export this class and make it configurable by the Server class. | ||
class CompressionMethodMap { | ||
constructor () { | ||
this.default = null; | ||
this.accepts = null; | ||
this.map = new Map(); | ||
this.register('identity', IdentityHandler); | ||
this.register('deflate', DeflateHandler); | ||
this.register('gzip', GzipHandler); | ||
this.setDefault('identity'); | ||
} | ||
switch (compressionName) { | ||
case 'identity' : | ||
return new IdentityHandler(); | ||
case 'deflate' : | ||
return new DeflateHandler(); | ||
case 'gzip' : | ||
return new GzipHandler(); | ||
default : | ||
register (compressionName, compressionMethodConstructor) { | ||
if (typeof compressionName !== 'string') { | ||
throw new TypeError('Compression method must be a string'); | ||
} | ||
if (typeof compressionMethodConstructor !== 'function') { | ||
throw new TypeError('Compression method constructor must be a function'); | ||
} | ||
this.map.set(compressionName, compressionMethodConstructor); | ||
this.accepts = Array.from(this.map.keys()); | ||
} | ||
setDefault (compressionName) { | ||
if (typeof compressionName !== 'string') { | ||
throw new TypeError('Compression method must be a string'); | ||
} | ||
if (!this.map.has(compressionName)) { | ||
// TODO: This error code must be UNIMPLEMENTED. | ||
throw new Error(`Compression method not supported: ${compressionName}`); | ||
} | ||
this.default = compressionName; | ||
} | ||
getDefaultInstance () { | ||
return this.getInstance(this.default); | ||
} | ||
getInstance (compressionName) { | ||
if (typeof compressionName !== 'string') { | ||
throw new TypeError('Compression method must be a string'); | ||
} | ||
const Ctor = this.map.get(compressionName); | ||
if (Ctor === undefined) { | ||
// TODO: This error code must be UNIMPLEMENTED. | ||
throw new Error(`Compression method not supported: ${compressionName}`); | ||
} | ||
return new Ctor(); | ||
} | ||
} | ||
const compressionMethods = new CompressionMethodMap(); | ||
const defaultCompression = compressionMethods.getDefaultInstance(); | ||
const defaultAcceptedEncoding = compressionMethods.accepts; | ||
class CompressionFilter { | ||
constructor () { | ||
this.send = new IdentityHandler(); | ||
this.receive = new IdentityHandler(); | ||
this.supportedMethods = compressionMethods; | ||
this.send = defaultCompression; | ||
this.receive = defaultCompression; | ||
this.accepts = defaultAcceptedEncoding; | ||
} | ||
sendMetadata (metadata) { // eslint-disable-line class-methods-use-this | ||
// TODO: These values shouldn't be hard coded. | ||
metadata.set(kGrpcEncodingHeader, 'identity'); | ||
metadata.set(kGrpcAcceptEncodingHeader, 'identity,deflate,gzip'); | ||
return metadata; | ||
sendHeaders () { | ||
return { | ||
[kGrpcEncodingHeader]: this.send.name, | ||
[kGrpcAcceptEncodingHeader]: this.accepts.join(',') | ||
}; | ||
} | ||
@@ -154,5 +217,26 @@ | ||
this.receive = getCompressionHandler(encoding); | ||
if (encoding !== this.receive.name) { | ||
this.receive = this.supportedMethods.getInstance(encoding); | ||
} | ||
} | ||
const acceptedEncoding = metadata.get(kGrpcAcceptEncodingHeader); | ||
if (acceptedEncoding.length > 0) { | ||
this.accepts = acceptedEncoding; | ||
} | ||
// Check that the client supports the incoming compression type. | ||
if (this.accepts.includes(this.receive.name)) { | ||
if (this.send.name !== this.receive.name) { | ||
this.send = this.supportedMethods.getInstance(this.receive.name); | ||
} | ||
} else { | ||
// The client does not support this compression type, so send | ||
// back uncompressed data. | ||
if (this.send.name !== 'identity') { | ||
this.send = this.supportedMethods.getInstance(this.receive.name); | ||
} | ||
} | ||
metadata.remove(kGrpcEncodingHeader); | ||
@@ -159,0 +243,0 @@ metadata.remove(kGrpcAcceptEncodingHeader); |
@@ -43,11 +43,6 @@ 'use strict'; | ||
let headers; | ||
const custom = customMetadata ? customMetadata.toHttp2Headers() : null; | ||
const headers = Object.assign(this.compression.sendHeaders(), | ||
defaultResponseHeaders, custom); | ||
if (customMetadata) { | ||
headers = Object.assign({}, defaultResponseHeaders, | ||
customMetadata.toHttp2Headers()); | ||
} else { | ||
headers = defaultResponseHeaders; | ||
} | ||
this.stream.once('wantTrailers', onWantTrailers.bind(this)); | ||
@@ -227,3 +222,3 @@ this.stream.respond(headers, defaultResponseOptions); | ||
[kGrpcStatusHeader]: this.status.code, | ||
[kGrpcMessageHeader]: this.status.details | ||
[kGrpcMessageHeader]: encodeURI(this.status.details) | ||
}; | ||
@@ -230,0 +225,0 @@ const metadata = this.status.metadata; |
{ | ||
"name": "grpc-server-js", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "Pure JavaScript gRPC Server", | ||
@@ -5,0 +5,0 @@ "author": "Colin J. Ihrig <cjihrig@gmail.com> (http://www.cjihrig.com/)", |
@@ -10,8 +10,34 @@ # grpc-server-js | ||
## Public API Deviations from `grpc.Server` | ||
## Documentation | ||
The goal is to be largely compatible with the existing [`Server`](https://grpc.io/grpc/node/grpc.Server.html) and [`ServerCredentials`](https://grpc.io/grpc/node/grpc.ServerCredentials.html) documentation. | ||
## Features | ||
- Unary calls. | ||
- Streaming client request calls. | ||
- Streaming server response calls. | ||
- Bidirectional streaming calls. | ||
- Deadline and cancellation support. | ||
- Support for gzip and deflate compression, as well as uncompressed messages. | ||
- The only third party dependency is [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js), which is used for some shared data structures. | ||
- No C++ dependencies. This implementation relies on Node's [`http2`](https://nodejs.org/api/http2.html) module. | ||
## Public API Deviations from the Existing `grpc.Server` | ||
- `Server.prototype.bind()` is an `async` function. | ||
- The deprecated `Server.prototype.addProtoService()` is not implemented. | ||
- `Server.prototype.addHttp2Port()` is not implemented. | ||
- `Server.prototype.forceShutdown()` is not implemented. | ||
- The `private_key` and `cert_chain` properties of `keyCertPair` instances have | ||
been renamed to `privateKey` and `certChain`. | ||
## Acknowledgement | ||
This module is heavily inspired by the [`grpc`](https://www.npmjs.com/package/grpc) native module. Some of the source code is adapted from the [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) module. | ||
## Useful References | ||
- [What is gRPC?](https://grpc.io/docs/guides/index.html) | ||
- [gRPC over HTTP2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) | ||
- [gRPC Compression](https://github.com/grpc/grpc/blob/master/doc/compression.md) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32528
893
43