From d4eeb1ff28ed20fbb3d81bdeda6588d27207a5c5 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Tue, 2 May 2017 15:29:48 +0800 Subject: [PATCH] sproto support response nil --- lualib-src/sproto/README.md | 59 ++++++++++++++++++++++++++++--------- lualib-src/sproto/lsproto.c | 10 +++++-- lualib-src/sproto/sproto.c | 13 ++++++++ lualib-src/sproto/sproto.h | 1 + lualib/sprotoparser.lua | 21 +++++++++---- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/lualib-src/sproto/README.md b/lualib-src/sproto/README.md index 67366a46..9fd4c3ff 100644 --- a/lualib-src/sproto/README.md +++ b/lualib-src/sproto/README.md @@ -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 * lua-cjson is a json library https://github.com/efelix/lua-cjson -Lua API +Parser ======= ```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. +Lua API +======= + ```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.querytype(sp, typename)` queries a type object from a sproto object by typename. -* `sproto.encode(st, luatable)` encodes a lua table by a type object, and generates a string message. -* `sproto.decode(st, message)` decodes a message string generated by sproto.encode with type. -* `sproto.pack(sprotomessage)` packs a string encoded by sproto.encode to reduce the size. -* `sproto.unpack(packedmessage)` unpacks the string packed by sproto.pack. - -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.new(spbin)` creates a sproto object by a schema binary string (generates by parser). +* `sprotocore.newproto(spbin)` creates a sproto c object by a schema binary string (generates by parser). +* `sproto.sharenew(spbin)` share a sproto object from a sproto c object (generates by sprotocore.newproto). +* `sproto.parse(schema)` creares a sproto object by a schema text string (by calling parser.parse) +* `sproto:exist_type(typename)` detect whether a type exist in sproto object. +* `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. +* `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 ======= 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. Schema Language @@ -137,6 +163,8 @@ The schema text is like this: } 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 { @@ -159,7 +187,7 @@ A schema text can be self-described by the sproto schema language. .field { name 0 : string 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 array 4 : boolean 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 request 2 : integer # index response 3 : integer # index + confirm 4 : boolean # response nil where confirm == true } .group { @@ -184,8 +213,9 @@ A schema text can be self-described by the sproto schema language. Types ======= -* **string** : binary string -* **integer** : integer, the max length of an integer is signed 64bit. +* **string** : string +* **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 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? -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? @@ -436,6 +466,7 @@ struct sproto_arg { int length; int index; // array base 1 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); diff --git a/lualib-src/sproto/lsproto.c b/lualib-src/sproto/lsproto.c index 11b1a67d..70437931 100644 --- a/lualib-src/sproto/lsproto.c +++ b/lualib-src/sproto/lsproto.c @@ -275,7 +275,9 @@ lencode(lua_State *L) { int tbl_index = 2; struct sproto_type * st = lua_touserdata(L, 1); 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_checkstack(L, ENCODE_DEEPLEVEL*2 + 8, NULL); @@ -573,7 +575,11 @@ lprotocol(lua_State *L) { } response = sproto_protoquery(sp, tag, SPROTO_RESPONSE); if (response == NULL) { - lua_pushnil(L); + if (sproto_protoresponse(sp, tag)) { + lua_pushlightuserdata(L, NULL); // response nil + } else { + lua_pushnil(L); + } } else { lua_pushlightuserdata(L, response); } diff --git a/lualib-src/sproto/sproto.c b/lualib-src/sproto/sproto.c index 416f1006..713f1569 100644 --- a/lualib-src/sproto/sproto.c +++ b/lualib-src/sproto/sproto.c @@ -32,6 +32,7 @@ struct sproto_type { struct protocol { const char *name; int tag; + int confirm; // confirm == 1 where response nil 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->p[SPROTO_REQUEST] = NULL; p->p[SPROTO_RESPONSE] = NULL; + p->confirm = 0; tag = 0; for (i=0;ip[SPROTO_RESPONSE] = &s->type[value]; break; + case 4: // confirm + p->confirm = value; + break; default: return NULL; } @@ -542,6 +547,8 @@ sproto_dump(struct sproto *s) { } if (p->p[SPROTO_RESPONSE]) { printf(" response:%s", p->p[SPROTO_RESPONSE]->name); + } else if (p->confirm) { + printf(" response nil"); } printf("\n"); } @@ -590,6 +597,12 @@ sproto_protoquery(const struct sproto *sp, int proto, int what) { 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 * sproto_protoname(const struct sproto *sp, int proto) { struct protocol * p = query_proto(sp, proto); diff --git a/lualib-src/sproto/sproto.h b/lualib-src/sproto/sproto.h index cf40497b..f70309ec 100644 --- a/lualib-src/sproto/sproto.h +++ b/lualib-src/sproto/sproto.h @@ -30,6 +30,7 @@ int sproto_prototag(const struct sproto *, const char * name); const char * sproto_protoname(const struct sproto *, int proto); // SPROTO_REQUEST(0) : request, SPROTO_RESPONSE(1): response 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); diff --git a/lualib/sprotoparser.lua b/lualib/sprotoparser.lua index 898a1cbc..4c9a7d07 100644 --- a/lualib/sprotoparser.lua +++ b/lualib/sprotoparser.lua @@ -104,7 +104,13 @@ function convert.protocol(all, obj) typename = obj[1] .. "." .. p[1] all.type[typename] = convert.type(all, { typename, struct }) 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 return result end @@ -259,6 +265,7 @@ end tag 1 : integer request 2 : integer # index response 3 : integer # index + confirm 4 : boolean # true means response nil } .group { @@ -366,18 +373,22 @@ local function packproto(name, p, alltypes) "\0\0", -- name (id=0, ref=0) packvalue(p.tag), -- tag (tag=1) } - if p.request == nil and p.response == nil then - tmp[1] = "\2\0" + if p.request == nil and p.response == nil and p.confirm == nil then + tmp[1] = "\2\0" -- only two fields else if p.request then table.insert(tmp, packvalue(alltypes[p.request].id)) -- request typename (tag=2) else - table.insert(tmp, "\1\0") + table.insert(tmp, "\1\0") -- skip this field (request) end if p.response then 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 - tmp[1] = "\3\0" + tmp[1] = "\3\0" -- only three fields end end