sproto support response nil

This commit is contained in:
Cloud Wu
2017-05-02 15:29:48 +08:00
parent fd427843a9
commit d4eeb1ff28
5 changed files with 83 additions and 21 deletions

View File

@@ -82,7 +82,7 @@ lua-cjson | 4.92s | 8.30s | 183 bytes
* pbc-lua is a google protocol buffers library https://github.com/cloudwu/pbc * pbc-lua is a google protocol buffers library https://github.com/cloudwu/pbc
* lua-cjson is a json library https://github.com/efelix/lua-cjson * lua-cjson is a json library https://github.com/efelix/lua-cjson
Lua API Parser
======= =======
```lua ```lua
@@ -93,24 +93,50 @@ local parser = require "sprotoparser"
The parser is needed for parsing the sproto schema. You can use it to generate binary string offline. The schema text and the parser is not needed when your program is running. The parser is needed for parsing the sproto schema. You can use it to generate binary string offline. The schema text and the parser is not needed when your program is running.
Lua API
=======
```lua ```lua
local sproto = require "sproto.core" local sproto = require "sproto"
local sprotocore = require "sproto.core" -- optional
``` ```
* `sproto.newproto(sp)` creates a sproto object by a schema string (generates by parser). * `sproto.new(spbin)` creates a sproto object by a schema binary string (generates by parser).
* `sproto.querytype(sp, typename)` queries a type object from a sproto object by typename. * `sprotocore.newproto(spbin)` creates a sproto c object by a schema binary string (generates by parser).
* `sproto.encode(st, luatable)` encodes a lua table by a type object, and generates a string message. * `sproto.sharenew(spbin)` share a sproto object from a sproto c object (generates by sprotocore.newproto).
* `sproto.decode(st, message)` decodes a message string generated by sproto.encode with type. * `sproto.parse(schema)` creares a sproto object by a schema text string (by calling parser.parse)
* `sproto.pack(sprotomessage)` packs a string encoded by sproto.encode to reduce the size. * `sproto:exist_type(typename)` detect whether a type exist in sproto object.
* `sproto.unpack(packedmessage)` unpacks the string packed by sproto.pack. * `sproto:encode(typename, luatable)` encodes a lua table with typename into a binary string.
* `sproto:decode(typename, blob [,sz])` decodes a binary string generated by sproto.encode with typename. If blob is a lightuserdata (C ptr), sz (integer) is needed.
The sproto supports protocol tag for RPC. Use `sproto.protocol(tagorname)` to convert the protocol name to the tag id, or convert back from tag id to the name, and returns the request/response message type objects of this protocol. * `sproto:pencode(typename, luatable)` The same with sproto:encode, but pack (compress) the results.
* `sproto:pdecode(typename, blob [,sz])` The same with sproto.decode, but unpack the blob (generated by sproto:pencode) first.
* `sproto:default(typename, type)` Create a table with default values of typename. Type can be nil , "REQUEST", or "RESPONSE".
RPC API RPC API
======= =======
There is a lua wrapper for the core API for RPC . There is a lua wrapper for the core API for RPC .
`sproto:host([packagename])` creates a host object to deliver the rpc message.
`host:dispatch(blob [,sz])` unpack and decode (sproto:pdecode) the binary string with type the host created (packagename).
If .type is exist, it's a REQUEST message with .type , returns "REQUEST", protoname, message, responser, .ud. The responser is a function for encode the response message. The responser will be nil when .session is not exist.
If .type is not exist, it's a RESPONSE message for .session . Returns "RESPONSE", .session, message, .ud .
`host:attach(sprotoobj)` creates a function(protoname, message, session, ud) to pack and encode request message with sprotoobj.
If you don't want to use host object, you can also use these following apis to encode and decode the rpc message:
`sproto:request_encode(protoname, tbl)` encode a request message with protoname.
`sproto:response_encode(protoname, tbl)` encode a response message with protoname.
`sproto:request_decode(protoname, blob [,sz])` decode a request message with protoname.
`sproto:response_decode(protoname, blob [,sz]` decode a response message with protoname.
Read testrpc.lua for detail. Read testrpc.lua for detail.
Schema Language Schema Language
@@ -137,6 +163,8 @@ The schema text is like this:
} }
phone 3 : *PhoneNumber # *PhoneNumber means an array of PhoneNumber. phone 3 : *PhoneNumber # *PhoneNumber means an array of PhoneNumber.
height 4 : integer(2) # (2) means a 1/100 fixed-point number.
data 5 : binary # Some binary data
} }
.AddressBook { .AddressBook {
@@ -159,7 +187,7 @@ A schema text can be self-described by the sproto schema language.
.field { .field {
name 0 : string name 0 : string
buildin 1 : integer buildin 1 : integer
type 2 : integer type 2 : integer # type is fixed-point number precision when buildin is SPROTO_TINTEGER; When buildin is SPROTO_TSTRING, it means binary string when type is 1.
tag 3 : integer tag 3 : integer
array 4 : boolean array 4 : boolean
key 5 : integer # If key exists, array must be true, and it's a map. key 5 : integer # If key exists, array must be true, and it's a map.
@@ -173,6 +201,7 @@ A schema text can be self-described by the sproto schema language.
tag 1 : integer tag 1 : integer
request 2 : integer # index request 2 : integer # index
response 3 : integer # index response 3 : integer # index
confirm 4 : boolean # response nil where confirm == true
} }
.group { .group {
@@ -184,8 +213,9 @@ A schema text can be self-described by the sproto schema language.
Types Types
======= =======
* **string** : binary string * **string** : string
* **integer** : integer, the max length of an integer is signed 64bit. * **binary** : binary string (it's a sub type of string)
* **integer** : integer, the max length of an integer is signed 64bit. It can be a fixed-point number with specified precision.
* **boolean** : true or false * **boolean** : true or false
You can add * before the typename to declare an array. You can add * before the typename to declare an array.
@@ -196,7 +226,7 @@ User defined type can be any name in alphanumeric characters except the build-in
* Where are double or real types? * Where are double or real types?
I have been using Google protocol buffers for many years in many projects, and I found the real types were seldom used. If you really need it, you can use string to serialize the double numbers. I have been using Google protocol buffers for many years in many projects, and I found the real types were seldom used. If you really need it, you can use string to serialize the double numbers. When you need decimal, you can specify the fixed-point presision.
* Where is enum? * Where is enum?
@@ -436,6 +466,7 @@ struct sproto_arg {
int length; int length;
int index; // array base 1 int index; // array base 1
int mainindex; // for map int mainindex; // for map
int extra; // SPROTO_TINTEGER: fixed-point presision ; SPROTO_TSTRING 0:utf8 string 1:binary
}; };
typedef int (*sproto_callback)(const struct sproto_arg *args); typedef int (*sproto_callback)(const struct sproto_arg *args);

View File

@@ -275,7 +275,9 @@ lencode(lua_State *L) {
int tbl_index = 2; int tbl_index = 2;
struct sproto_type * st = lua_touserdata(L, 1); struct sproto_type * st = lua_touserdata(L, 1);
if (st == NULL) { if (st == NULL) {
return luaL_argerror(L, 1, "Need a sproto_type object"); luaL_checktype(L, tbl_index, LUA_TNIL);
lua_pushstring(L, "");
return 1; // response nil
} }
luaL_checktype(L, tbl_index, LUA_TTABLE); luaL_checktype(L, tbl_index, LUA_TTABLE);
luaL_checkstack(L, ENCODE_DEEPLEVEL*2 + 8, NULL); luaL_checkstack(L, ENCODE_DEEPLEVEL*2 + 8, NULL);
@@ -573,7 +575,11 @@ lprotocol(lua_State *L) {
} }
response = sproto_protoquery(sp, tag, SPROTO_RESPONSE); response = sproto_protoquery(sp, tag, SPROTO_RESPONSE);
if (response == NULL) { if (response == NULL) {
lua_pushnil(L); if (sproto_protoresponse(sp, tag)) {
lua_pushlightuserdata(L, NULL); // response nil
} else {
lua_pushnil(L);
}
} else { } else {
lua_pushlightuserdata(L, response); lua_pushlightuserdata(L, response);
} }

View File

@@ -32,6 +32,7 @@ struct sproto_type {
struct protocol { struct protocol {
const char *name; const char *name;
int tag; int tag;
int confirm; // confirm == 1 where response nil
struct sproto_type * p[2]; struct sproto_type * p[2];
}; };
@@ -364,6 +365,7 @@ import_protocol(struct sproto *s, struct protocol *p, const uint8_t * stream) {
p->tag = -1; p->tag = -1;
p->p[SPROTO_REQUEST] = NULL; p->p[SPROTO_REQUEST] = NULL;
p->p[SPROTO_RESPONSE] = NULL; p->p[SPROTO_RESPONSE] = NULL;
p->confirm = 0;
tag = 0; tag = 0;
for (i=0;i<fn;i++,tag++) { for (i=0;i<fn;i++,tag++) {
int value = toword(stream + SIZEOF_FIELD * i); int value = toword(stream + SIZEOF_FIELD * i);
@@ -395,6 +397,9 @@ import_protocol(struct sproto *s, struct protocol *p, const uint8_t * stream) {
return NULL; return NULL;
p->p[SPROTO_RESPONSE] = &s->type[value]; p->p[SPROTO_RESPONSE] = &s->type[value];
break; break;
case 4: // confirm
p->confirm = value;
break;
default: default:
return NULL; return NULL;
} }
@@ -542,6 +547,8 @@ sproto_dump(struct sproto *s) {
} }
if (p->p[SPROTO_RESPONSE]) { if (p->p[SPROTO_RESPONSE]) {
printf(" response:%s", p->p[SPROTO_RESPONSE]->name); printf(" response:%s", p->p[SPROTO_RESPONSE]->name);
} else if (p->confirm) {
printf(" response nil");
} }
printf("\n"); printf("\n");
} }
@@ -590,6 +597,12 @@ sproto_protoquery(const struct sproto *sp, int proto, int what) {
return NULL; return NULL;
} }
int
sproto_protoresponse(const struct sproto * sp, int proto) {
struct protocol * p = query_proto(sp, proto);
return (p!=NULL && (p->p[SPROTO_RESPONSE] || p->confirm));
}
const char * const char *
sproto_protoname(const struct sproto *sp, int proto) { sproto_protoname(const struct sproto *sp, int proto) {
struct protocol * p = query_proto(sp, proto); struct protocol * p = query_proto(sp, proto);

View File

@@ -30,6 +30,7 @@ int sproto_prototag(const struct sproto *, const char * name);
const char * sproto_protoname(const struct sproto *, int proto); const char * sproto_protoname(const struct sproto *, int proto);
// SPROTO_REQUEST(0) : request, SPROTO_RESPONSE(1): response // SPROTO_REQUEST(0) : request, SPROTO_RESPONSE(1): response
struct sproto_type * sproto_protoquery(const struct sproto *, int proto, int what); struct sproto_type * sproto_protoquery(const struct sproto *, int proto, int what);
int sproto_protoresponse(const struct sproto *, int proto);
struct sproto_type * sproto_type(const struct sproto *, const char * type_name); struct sproto_type * sproto_type(const struct sproto *, const char * type_name);

View File

@@ -104,7 +104,13 @@ function convert.protocol(all, obj)
typename = obj[1] .. "." .. p[1] typename = obj[1] .. "." .. p[1]
all.type[typename] = convert.type(all, { typename, struct }) all.type[typename] = convert.type(all, { typename, struct })
end end
result[p[1]] = typename if typename == "nil" then
if p[1] == "response" then
result.confirm = true
end
else
result[p[1]] = typename
end
end end
return result return result
end end
@@ -259,6 +265,7 @@ end
tag 1 : integer tag 1 : integer
request 2 : integer # index request 2 : integer # index
response 3 : integer # index response 3 : integer # index
confirm 4 : boolean # true means response nil
} }
.group { .group {
@@ -366,18 +373,22 @@ local function packproto(name, p, alltypes)
"\0\0", -- name (id=0, ref=0) "\0\0", -- name (id=0, ref=0)
packvalue(p.tag), -- tag (tag=1) packvalue(p.tag), -- tag (tag=1)
} }
if p.request == nil and p.response == nil then if p.request == nil and p.response == nil and p.confirm == nil then
tmp[1] = "\2\0" tmp[1] = "\2\0" -- only two fields
else else
if p.request then if p.request then
table.insert(tmp, packvalue(alltypes[p.request].id)) -- request typename (tag=2) table.insert(tmp, packvalue(alltypes[p.request].id)) -- request typename (tag=2)
else else
table.insert(tmp, "\1\0") table.insert(tmp, "\1\0") -- skip this field (request)
end end
if p.response then if p.response then
table.insert(tmp, packvalue(alltypes[p.response].id)) -- request typename (tag=3) table.insert(tmp, packvalue(alltypes[p.response].id)) -- request typename (tag=3)
elseif p.confirm then
tmp[1] = "\5\0" -- add confirm field
table.insert(tmp, "\1\0") -- skip this field (response)
table.insert(tmp, packvalue(1)) -- confirm = true
else else
tmp[1] = "\3\0" tmp[1] = "\3\0" -- only three fields
end end
end end