mirror of https://github.com/grpc/grpc-node.git
Native: Add initial metadata options
This commit is contained in:
parent
9bbe7057b5
commit
fe090a089a
|
@ -81,8 +81,13 @@ Local<Value> nanErrorWithCode(const char *msg, grpc_call_error code) {
|
|||
return scope.Escape(err);
|
||||
}
|
||||
|
||||
bool CreateMetadataArray(Local<Object> metadata, grpc_metadata_array *array) {
|
||||
bool CreateMetadataArray(Local<Object> metadata_obj, grpc_metadata_array *array) {
|
||||
HandleScope scope;
|
||||
Local<Value> metadata_value = (Nan::Get(metadata_obj, Nan::New("metadata").ToLocalChecked())).ToLocalChecked();
|
||||
if (!metadata_value->IsObject()) {
|
||||
return false;
|
||||
}
|
||||
Local<Object> metadata = Nan::To<Object>(metadata_value).ToLocalChecked();
|
||||
Local<Array> keys = Nan::GetOwnPropertyNames(metadata).ToLocalChecked();
|
||||
for (unsigned int i = 0; i < keys->Length(); i++) {
|
||||
Local<String> current_key =
|
||||
|
@ -159,7 +164,10 @@ Local<Value> ParseMetadata(const grpc_metadata_array *metadata_array) {
|
|||
Nan::Set(array, array->Length(), CopyStringFromSlice(elem->value));
|
||||
}
|
||||
}
|
||||
return scope.Escape(metadata_object);
|
||||
Local<Object> result = Nan::New<Object>();
|
||||
Nan::Set(result, Nan::New("metadata").ToLocalChecked(), metadata_object);
|
||||
Nan::Set(result, Nan::New("flags").ToLocalChecked(), Nan::New<v8::Uint32>(0));
|
||||
return scope.Escape(result);
|
||||
}
|
||||
|
||||
Local<Value> Op::GetOpType() const {
|
||||
|
@ -185,7 +193,17 @@ class SendMetadataOp : public Op {
|
|||
if (maybe_metadata.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!CreateMetadataArray(maybe_metadata.ToLocalChecked(), &send_metadata)) {
|
||||
Local<Object> metadata_object = maybe_metadata.ToLocalChecked();
|
||||
MaybeLocal<Value> maybe_flag_value =
|
||||
Nan::Get(metadata_object, Nan::New("flags").ToLocalChecked());
|
||||
if (!maybe_flag_value.IsEmpty()) {
|
||||
Local<Value> flag_value = maybe_flag_value.ToLocalChecked();
|
||||
if (flag_value->IsUint32()) {
|
||||
Maybe<uint32_t> maybe_flag = Nan::To<uint32_t>(flag_value);
|
||||
out->flags |= maybe_flag.FromMaybe(0) & GRPC_INITIAL_METADATA_USED_MASK;
|
||||
}
|
||||
}
|
||||
if (!CreateMetadataArray(metadata_object, &send_metadata)) {
|
||||
return false;
|
||||
}
|
||||
out->data.send_initial_metadata.count = send_metadata.count;
|
||||
|
@ -225,7 +243,7 @@ class SendMessageOp : public Op {
|
|||
Local<Value> flag_value = maybe_flag_value.ToLocalChecked();
|
||||
if (flag_value->IsUint32()) {
|
||||
Maybe<uint32_t> maybe_flag = Nan::To<uint32_t>(flag_value);
|
||||
out->flags = maybe_flag.FromMaybe(0) & GRPC_WRITE_USED_MASK;
|
||||
out->flags |= maybe_flag.FromMaybe(0) & GRPC_WRITE_USED_MASK;
|
||||
}
|
||||
}
|
||||
send_message = BufferToByteBuffer(value);
|
||||
|
|
|
@ -489,10 +489,29 @@ declare module "grpc" {
|
|||
type sendUnaryData<ResponseType> =
|
||||
(error: ServiceError | null, value: ResponseType | null, trailer?: Metadata, flags?: number) => void;
|
||||
|
||||
interface MetadataOptions {
|
||||
/* Signal that the request is idempotent. Defaults to false */
|
||||
idempotentRequest?: boolean;
|
||||
/* Signal that the call should not return UNAVAILABLE before it has
|
||||
* started. Defaults to true. */
|
||||
waitForReady?: boolean;
|
||||
/* Signal that the call is cacheable. GRPC is free to use GET verb.
|
||||
* Defaults to false */
|
||||
cacheableRequest?: boolean;
|
||||
/* Signal that the initial metadata should be corked. Defaults to false. */
|
||||
corked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for storing metadata. Keys are normalized to lowercase ASCII.
|
||||
*/
|
||||
export class Metadata {
|
||||
/**
|
||||
* @param options Boolean options for the beginning of the call.
|
||||
* These options only have any effect when passed at the beginning of
|
||||
* a client request.
|
||||
*/
|
||||
constructor(options?: MetadataOptions);
|
||||
/**
|
||||
* Sets the given value for the given key by replacing any other values
|
||||
* associated with that key. Normalizes the key.
|
||||
|
@ -536,6 +555,14 @@ declare module "grpc" {
|
|||
* @return The newly cloned object.
|
||||
*/
|
||||
clone(): Metadata;
|
||||
|
||||
/**
|
||||
* Set options on the metadata object
|
||||
* @param options Boolean options for the beginning of the call.
|
||||
* These options only have any effect when passed at the beginning of
|
||||
* a client request.
|
||||
*/
|
||||
setOptions(options: MetadataOptions);
|
||||
}
|
||||
|
||||
export type MetadataValue = string | Buffer;
|
||||
|
|
|
@ -22,18 +22,36 @@ var clone = require('lodash.clone');
|
|||
|
||||
var grpc = require('./grpc_extension');
|
||||
|
||||
const IDEMPOTENT_REQUEST_FLAG = 0x10;
|
||||
const WAIT_FOR_READY_FLAG = 0x20;
|
||||
const CACHEABLE_REQUEST_FLAG = 0x40;
|
||||
const WAIT_FOR_READY_EXPLICITLY_SET_FLAG = 0x80;
|
||||
const CORKED_FLAG = 0x100;
|
||||
|
||||
/**
|
||||
* Class for storing metadata. Keys are normalized to lowercase ASCII.
|
||||
* @memberof grpc
|
||||
* @constructor
|
||||
* @param {Object=} options Boolean options for the beginning of the call.
|
||||
* These options only have any effect when passed at the beginning of
|
||||
* a client request.
|
||||
* @param {boolean=} [options.idempotentRequest=false] Signal that the request
|
||||
* is idempotent
|
||||
* @param {boolean=} [options.waitForReady=true] Signal that the call should
|
||||
* not return UNAVAILABLE before it has started.
|
||||
* @param {boolean=} [options.cacheableRequest=false] Signal that the call is
|
||||
* cacheable. GRPC is free to use GET verb.
|
||||
* @param {boolean=} [options.corked=false] Signal that the initial metadata
|
||||
* should be corked.
|
||||
* @example
|
||||
* var metadata = new metadata_module.Metadata();
|
||||
* metadata.set('key1', 'value1');
|
||||
* metadata.add('key1', 'value2');
|
||||
* metadata.get('key1') // returns ['value1', 'value2']
|
||||
*/
|
||||
function Metadata() {
|
||||
function Metadata(options) {
|
||||
this._internal_repr = {};
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
function normalizeKey(key) {
|
||||
|
@ -141,34 +159,82 @@ Metadata.prototype.clone = function() {
|
|||
const value = this._internal_repr[key];
|
||||
copy._internal_repr[key] = clone(value);
|
||||
});
|
||||
copy.flags = this.flags;
|
||||
return copy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set options on the metadata object
|
||||
* @param {Object} options Boolean options for the beginning of the call.
|
||||
* These options only have any effect when passed at the beginning of
|
||||
* a client request.
|
||||
* @param {boolean=} [options.idempotentRequest=false] Signal that the request
|
||||
* is idempotent
|
||||
* @param {boolean=} [options.waitForReady=true] Signal that the call should
|
||||
* not return UNAVAILABLE before it has started.
|
||||
* @param {boolean=} [options.cacheableRequest=false] Signal that the call is
|
||||
* cacheable. GRPC is free to use GET verb.
|
||||
* @param {boolean=} [options.corked=false] Signal that the initial metadata
|
||||
* should be corked.
|
||||
*/
|
||||
Metadata.prototype.setOptions = function(options) {
|
||||
let flags = 0;
|
||||
if (options) {
|
||||
if (options.idempotentRequest) {
|
||||
flags |= IDEMPOTENT_REQUEST_FLAG;
|
||||
}
|
||||
if (options.hasOwnProperty('waitForReady')) {
|
||||
flags |= WAIT_FOR_READY_EXPLICITLY_SET_FLAG;
|
||||
if (options.waitForReady) {
|
||||
flags |= WAIT_FOR_READY_FLAG;
|
||||
}
|
||||
}
|
||||
if (options.cacheableRequest) {
|
||||
flags |= CACHEABLE_REQUEST_FLAG;
|
||||
}
|
||||
if (options.corked) {
|
||||
flags |= CORKED_FLAG;
|
||||
}
|
||||
}
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata representation as passed to and the native addon
|
||||
* @typedef {object} grpc~CoreMetadata
|
||||
* @param {Object.<String, Array.<String|Buffer>>} metadata The metadata
|
||||
* @param {number} flags Metadata flags
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the metadata in the format used by interal code. Intended for internal
|
||||
* use only. API stability is not guaranteed.
|
||||
* @private
|
||||
* @return {Object.<String, Array.<String|Buffer>>} The metadata
|
||||
* @return {grpc~CoreMetadata} The metadata
|
||||
*/
|
||||
Metadata.prototype._getCoreRepresentation = function() {
|
||||
return this._internal_repr;
|
||||
return {
|
||||
metadata: this._internal_repr,
|
||||
flags: this.flags
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Metadata object from a metadata map in the internal format.
|
||||
* Intended for internal use only. API stability is not guaranteed.
|
||||
* @private
|
||||
* @param {Object.<String, Array.<String|Buffer>>} The metadata
|
||||
* @param {grpc~CoreMetadata} metadata The metadata object from core
|
||||
* @return {Metadata} The new Metadata object
|
||||
*/
|
||||
Metadata._fromCoreRepresentation = function(metadata) {
|
||||
var newMetadata = new Metadata();
|
||||
if (metadata) {
|
||||
Object.keys(metadata).forEach(key => {
|
||||
const value = metadata[key];
|
||||
Object.keys(metadata.metadata).forEach(key => {
|
||||
const value = metadata.metadata[key];
|
||||
newMetadata._internal_repr[key] = clone(value);
|
||||
});
|
||||
}
|
||||
newMetadata.flags = metadata.flags;
|
||||
return newMetadata;
|
||||
};
|
||||
|
||||
|
|
|
@ -128,8 +128,9 @@ describe('call', function() {
|
|||
var call = channel.createCall('method', getDeadline(1));
|
||||
assert.doesNotThrow(function() {
|
||||
var batch = {};
|
||||
batch[grpc.opType.SEND_INITIAL_METADATA] = {'key1': ['value1'],
|
||||
'key2': ['value2']};
|
||||
batch[grpc.opType.SEND_INITIAL_METADATA] = {
|
||||
metadata: {'key1': ['value1'], 'key2': ['value2']}
|
||||
};
|
||||
call.startBatch(batch, function(err, resp) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(resp, {'send_metadata': true});
|
||||
|
@ -142,8 +143,10 @@ describe('call', function() {
|
|||
assert.doesNotThrow(function() {
|
||||
var batch = {};
|
||||
batch[grpc.opType.SEND_INITIAL_METADATA] = {
|
||||
'key1-bin': [Buffer.from('value1')],
|
||||
'key2-bin': [Buffer.from('value2')]
|
||||
metadata: {
|
||||
'key1-bin': [Buffer.from('value1')],
|
||||
'key2-bin': [Buffer.from('value2')]
|
||||
}
|
||||
};
|
||||
call.startBatch(batch, function(err, resp) {
|
||||
assert.ifError(err);
|
||||
|
@ -174,6 +177,11 @@ describe('call', function() {
|
|||
batch[grpc.opType.SEND_INITIAL_METADATA] = 5;
|
||||
call.startBatch(batch, function(){});
|
||||
}, TypeError);
|
||||
assert.throws(function() {
|
||||
var batch = {};
|
||||
batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
call.startBatch(batch, function(){});
|
||||
}, TypeError);
|
||||
});
|
||||
});
|
||||
describe('startBatch with message', function() {
|
||||
|
|
|
@ -65,7 +65,7 @@ describe('end-to-end', function() {
|
|||
'dummy_method',
|
||||
Infinity);
|
||||
var client_batch = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
|
||||
|
@ -74,11 +74,17 @@ describe('end-to-end', function() {
|
|||
assert.deepEqual(response, {
|
||||
send_metadata: true,
|
||||
client_close: true,
|
||||
metadata: {},
|
||||
metadata: {
|
||||
metadata: {},
|
||||
flags: 0
|
||||
},
|
||||
status: {
|
||||
code: constants.status.OK,
|
||||
details: status_text,
|
||||
metadata: {}
|
||||
metadata: {
|
||||
metadata: {},
|
||||
flags: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
done();
|
||||
|
@ -90,9 +96,9 @@ describe('end-to-end', function() {
|
|||
var server_call = new_call.call;
|
||||
assert.notEqual(server_call, null);
|
||||
var server_batch = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||
metadata: {},
|
||||
metadata: {metadata: {}},
|
||||
code: constants.status.OK,
|
||||
details: status_text
|
||||
};
|
||||
|
@ -116,7 +122,9 @@ describe('end-to-end', function() {
|
|||
Infinity);
|
||||
var client_batch = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {
|
||||
client_key: ['client_value']
|
||||
metadata: {
|
||||
client_key: ['client_value']
|
||||
}
|
||||
};
|
||||
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||
|
@ -126,10 +134,13 @@ describe('end-to-end', function() {
|
|||
assert.deepEqual(response,{
|
||||
send_metadata: true,
|
||||
client_close: true,
|
||||
metadata: {server_key: ['server_value']},
|
||||
metadata: {metadata: {
|
||||
server_key: ['server_value']},
|
||||
flags: 0
|
||||
},
|
||||
status: {code: constants.status.OK,
|
||||
details: status_text,
|
||||
metadata: {}}
|
||||
metadata: {metadata: {}, flags: 0}}
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
@ -137,16 +148,18 @@ describe('end-to-end', function() {
|
|||
server.requestCall(function(err, call_details) {
|
||||
var new_call = call_details.new_call;
|
||||
assert.notEqual(new_call, null);
|
||||
assert.strictEqual(new_call.metadata.client_key[0],
|
||||
assert.strictEqual(new_call.metadata.metadata.client_key[0],
|
||||
'client_value');
|
||||
var server_call = new_call.call;
|
||||
assert.notEqual(server_call, null);
|
||||
var server_batch = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {
|
||||
server_key: ['server_value']
|
||||
metadata: {
|
||||
server_key: ['server_value']
|
||||
}
|
||||
};
|
||||
server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||
metadata: {},
|
||||
metadata: {metadata: {}},
|
||||
code: constants.status.OK,
|
||||
details: status_text
|
||||
};
|
||||
|
@ -171,7 +184,7 @@ describe('end-to-end', function() {
|
|||
'dummy_method',
|
||||
Infinity);
|
||||
var client_batch = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
client_batch[grpc.opType.SEND_MESSAGE] = Buffer.from(req_text);
|
||||
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||
|
@ -181,12 +194,12 @@ describe('end-to-end', function() {
|
|||
assert.ifError(err);
|
||||
assert(response.send_metadata);
|
||||
assert(response.client_close);
|
||||
assert.deepEqual(response.metadata, {});
|
||||
assert.deepEqual(response.metadata, {metadata: {}, flags: 0});
|
||||
assert(response.send_message);
|
||||
assert.strictEqual(response.read.toString(), reply_text);
|
||||
assert.deepEqual(response.status, {code: constants.status.OK,
|
||||
details: status_text,
|
||||
metadata: {}});
|
||||
metadata: {metadata: {}, flags: 0}});
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -196,7 +209,7 @@ describe('end-to-end', function() {
|
|||
var server_call = new_call.call;
|
||||
assert.notEqual(server_call, null);
|
||||
var server_batch = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
server_batch[grpc.opType.RECV_MESSAGE] = true;
|
||||
server_call.startBatch(server_batch, function(err, response) {
|
||||
assert.ifError(err);
|
||||
|
@ -205,7 +218,7 @@ describe('end-to-end', function() {
|
|||
var response_batch = {};
|
||||
response_batch[grpc.opType.SEND_MESSAGE] = Buffer.from(reply_text);
|
||||
response_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||
metadata: {},
|
||||
metadata: {metadata: {}},
|
||||
code: constants.status.OK,
|
||||
details: status_text
|
||||
};
|
||||
|
@ -226,7 +239,7 @@ describe('end-to-end', function() {
|
|||
'dummy_method',
|
||||
Infinity);
|
||||
var client_batch = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
client_batch[grpc.opType.SEND_MESSAGE] = Buffer.from(requests[0]);
|
||||
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||
call.startBatch(client_batch, function(err, response) {
|
||||
|
@ -234,7 +247,7 @@ describe('end-to-end', function() {
|
|||
assert.deepEqual(response, {
|
||||
send_metadata: true,
|
||||
send_message: true,
|
||||
metadata: {}
|
||||
metadata: {metadata: {}, flags: 0}
|
||||
});
|
||||
var req2_batch = {};
|
||||
req2_batch[grpc.opType.SEND_MESSAGE] = Buffer.from(requests[1]);
|
||||
|
@ -248,7 +261,7 @@ describe('end-to-end', function() {
|
|||
status: {
|
||||
code: constants.status.OK,
|
||||
details: status_text,
|
||||
metadata: {}
|
||||
metadata: {metadata: {}, flags: 0}
|
||||
}
|
||||
});
|
||||
done();
|
||||
|
@ -261,7 +274,7 @@ describe('end-to-end', function() {
|
|||
var server_call = new_call.call;
|
||||
assert.notEqual(server_call, null);
|
||||
var server_batch = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {metadata: {}};
|
||||
server_batch[grpc.opType.RECV_MESSAGE] = true;
|
||||
server_call.startBatch(server_batch, function(err, response) {
|
||||
assert.ifError(err);
|
||||
|
@ -275,7 +288,7 @@ describe('end-to-end', function() {
|
|||
var end_batch = {};
|
||||
end_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
|
||||
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||
metadata: {},
|
||||
metadata: {metadata: {}},
|
||||
code: constants.status.OK,
|
||||
details: status_text
|
||||
};
|
||||
|
|
|
@ -934,6 +934,19 @@ describe('Other conditions', function() {
|
|||
call.write({});
|
||||
call.end();
|
||||
});
|
||||
it('client should drop a call if not connected with waitForReady off', function(done) {
|
||||
/* We have to wait for the client to reach the first connection timeout
|
||||
* and go to TRANSIENT_FAILURE to confirm that the waitForReady option
|
||||
* makes it end the call instead of continuing to try. A DNS resolution
|
||||
* failure makes that transition very fast. */
|
||||
const disconnectedClient = new Client('nothing.invalid:50051', grpc.credentials.createInsecure());
|
||||
const metadata = new grpc.Metadata({waitForReady: false});
|
||||
disconnectedClient.unary({}, metadata, (error, value) =>{
|
||||
assert(error);
|
||||
assert.strictEqual(error.code, grpc.status.UNAVAILABLE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe('Server recieving bad input', function() {
|
||||
var misbehavingClient;
|
||||
var badArg = Buffer.from([0xFF]);
|
||||
|
|
Loading…
Reference in New Issue