{"id":13776378,"url":"https://github.com/ysugimoto/lua-resty-grpc-gateway","last_synced_at":"2025-04-28T10:17:36.936Z","repository":{"id":39549224,"uuid":"183222573","full_name":"ysugimoto/lua-resty-grpc-gateway","owner":"ysugimoto","description":"REST \u003c-\u003e gRPC gateway library implementation with OpenResty","archived":false,"fork":false,"pushed_at":"2023-10-25T12:51:43.000Z","size":1307,"stargazers_count":86,"open_issues_count":18,"forks_count":18,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-02-13T00:50:35.715Z","etag":null,"topics":["grpc","grpc-web","lua","openresty"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ysugimoto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-04-24T12:17:44.000Z","updated_at":"2024-02-13T00:50:35.716Z","dependencies_parsed_at":"2024-01-13T09:36:01.878Z","dependency_job_id":"e5e19108-64ec-4334-b2ec-441d24e6a738","html_url":"https://github.com/ysugimoto/lua-resty-grpc-gateway","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysugimoto%2Flua-resty-grpc-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysugimoto%2Flua-resty-grpc-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysugimoto%2Flua-resty-grpc-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysugimoto%2Flua-resty-grpc-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ysugimoto","download_url":"https://codeload.github.com/ysugimoto/lua-resty-grpc-gateway/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235388654,"owners_count":18981944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grpc","grpc-web","lua","openresty"],"created_at":"2024-08-03T18:00:24.340Z","updated_at":"2025-01-24T04:40:52.203Z","avatar_url":"https://github.com/ysugimoto.png","language":"Lua","funding_links":[],"categories":["Libraries","Lua"],"sub_categories":[],"readme":"[![CircleCI](https://circleci.com/gh/ysugimoto/lua-resty-grpc-gateway.svg?style=svg)](https://circleci.com/gh/ysugimoto/lua-resty-grpc-gateway)\n\n# lua-resty-grpc-gateway\n\nThis package provides request transformation between REST \u0026lt;-\u0026gt; gRPC with [Openresty](https://openresty.org/).\n\n## Motivation\n\nNginx supports `grpc-web` proxy since version 1.13.0, and Openresty 1.15.8.1 uses Nginx core 1.15.8.\n\nBut it cannot proxy with REST interface, so we'd like to support it with minimum Lua script support like [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway).\n\nThis just work for simple gateway, so you don't bound by golang. You can choose gRPC backend which built with any language!\n\nFor grpc-web detail, see [grpc-web Repository](https://github.com/grpc/grpc-web).\n\n## Requirement\n\n- Openresty 1.15.8.1 or later\n\n## Installation\n\nYou can install via `luarocks`.\n\n```\nluarocks install lua-resty-grpc-gateway\n```\n\n## Important for gRPC-Web proxy\n\nNote that nginx grpc gateway accepts only `grpcweb` mode, not `grpcwebtext`.\nSo usually you should compile protobuf with `--grpc-web_out=import_style=xxx,mode=grpcweb:$OUT_DIR`.\n\nBut this package also support `grpcwebtext` mode for simply using gRPC-Web proxy :v: If you want to use this mode, use polyfill.\n\nSee [polyfill-grpc-web-text-mode](https://github.com/ysugimoto/lua-resty-grpc-gateway#polyfill-grpc-web-text-mode) section.\n\n## Usage for simple gRPC-Web gateway\n\nThis is same as nginx's example. see [nginx documentation](https://www.nginx.com/blog/nginx-1-13-10-grpc/)\n\n## Usage for REST to gRPC\n\nIn order to transform from REST to gRPC completely, you need to use three of hook points:\n\n- `access_by_lua_*` to transform REST to gRPC request format\n- `body_filter_by_lua_* ` to transform from gRPC binary response to JSON format\n- `header_filter_by_lua_*` add `Content-Type: application/json` response header\n\n### nginx.conf\n\n```lua\n## 0. prepare proto file import_paths\ninit_by_lua_block {\n  PROTOC_IMPORT_PATHS = {\n    \"/usr/local/include\"\n  }\n}\n\nserver {\n  listen 80;\n  server_name localhost;\n\n  location /some-rest-endpoint {\n\n## 1. Transform request from REST to gRPC\n    access_by_lua_block {\n      local proto = require(\"grpc-gateway.proto\")\n      local grequest = require(\"grpc-gateway.request\")\n      local p, err = proto.new(\"/etc/proto/helloworld.proto\")\n      if err then\n        ngx.log(ngx.ERR, (\"proto load error: %s\"):format(err))\n        return\n      end\n      local req = grequest.new(p)\n      err = req:transform(\"helloworld.Greeter\", \"SayHello\")\n      if err then\n        ngx.log(ngx.ERR, (\"transform request error: %s\"):format(err))\n        return\n      end\n    }\n\n## 2. Transform response from gPRC to JSON\n    body_filter_by_lua_block {\n      local proto = require(\"grpc-gateway.proto\")\n      local gresponse = require(\"grpc-gateway.response\")\n      local p, err = proto.new(\"/etc/proto/helloworld.proto\")\n      if err then\n        ngx.log(ngx.ERR, (\"proto load error: %s\"):format(err))\n        return\n      end\n      local resp = gresponse.new(p)\n      err = resp:transform(\"helloworld.Greeter\", \"SayHello\")\n      if err then\n        ngx.log(ngx.ERR, (\"transform response error: %s\"):format(err))\n        return\n      end\n    }\n\n## 3. Swap response header to `Content-Type: application/json`\n    header_filter_by_lua_block {\n      ngx.header[\"Content-Type\"] = \"application/json\"\n    }\n\n    grpc_set_header Content-Type application/grpc;\n    grpc_pass localhost:9000;\n  }\n}\n```\n\n### helloworld.proto\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  string name = 1;\n}\n\nmessage HelloReply {\n  string message = 1;\n}\n```\n\nSee complete [example](https://github.com/ysugimoto/lua-resty-grpc-gateway/tree/master/example) for actual working.\n\n## Request transforming\n\n```lua\n-- load protobuf wrapper and request transformer\nlocal proto = require(\"grpc-gateway.proto\")\nlocal request = require(\"grpc-gateway.request\")\n\n-- First, instantiate protobuf wrapper with destination pb file\nlocal p, err = proto.new(\"/path/to/proto.file\")\nif err then\n  print(err) -- err is not null if file not found or something\nend\n\n-- Second, instatiate request with protobuf instance\nlocal r = request.new(p)\n\n-- Third, call transform() method. transform() method arguments are:\n--   first argument is service name (contains package name if you defined)\n--   second argument is RPC method name\n-- In this case, package will transform to helloworld.Greeter/SayHello request format of HelloRequest\nerr = r:transform(\"helloworld.Greeter\", \"SayHello\")\nif err then\n  print(err) -- err is not null if failed to transform request\nend\n```\n\nREST to gRPC request transformation supports `GET` and `POST` request methods, it means gRPC message is built from either of:\n\n- `GET`: use query string\n- `POST`: use post fields\n- `JSON POST`: use decoded JSON request body\n\nFor instance:\n\n```\nmessage HelloRequest {\n  string name = 1;\n}\n```\n\nFor above message structure, `name` field will be assigned by either of following way:\n\n```\nGET /?name=example\n```\n\n```\nPOST /\n\nname=example\n```\n\n```\nPOST /\nContent-Type: application/json\n\n{\"name\":\"example\"}\n```\n\nYou *DO NOT* specify all fields as empty, otherwise gateway will respond error.\n\n```\nGET /\n\u003e\u003e error\n```\n\n## Response transforming\n\n```lua\n-- load protobuf wrapper and response transformer\nlocal proto = require(\"grpc-gateway.proto\")\nlocal response = require(\"grpc-gateway.response\")\n\n-- First, instantiate protobuf wrapper with destination pb file\nlocal p, err = proto.new(\"/path/to/proto.file\")\nif err then\n  print(err) -- err is not null if file not found or something\nend\n\n-- Second, instatiate response with protobuf instance\nlocal r = response.new(p)\n\n-- Third, call transform() method as same as request:\n--   first argument is service name (contains package name if you defined)\n--   second argument is RPC method name\n-- In this case, package will transform to helloworld.Greeter/SayHello response format of HelloReply\nerr = r:transform(\"helloworld.Greeter\", \"SayHello\")\nif err then\n  print(err) -- err is not null if failed to transform response\nend\n```\n\nAnd, to pass a request to gRPC backend, nginx need to set `Content-Type` as `application/grpc`, then this header will be kept to REST response.\n\nTo avoid it, you need to swap this header on `header_filter_by_lua_*` phase:\n\n```lua\nheader_filter_by_lua_block {\n  ngx.header[\"Content-Type\"] = \"application/json\"\n}\n```\n\nOtherwise, REST HTTP response's `Content-Type` becomes `application/grpc`. Normally it's a bad way of process response (e.g. show download dialog on browser)\n\n## Import paths\n\nOne of important thing, in this package, you should define `import_paths` which is set when load exteral/additional proto files in your proto file.\n\nFor instance:\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\n// import dependent proto file\nimport \"google/protobuf/timestamp.proto\";\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  string name = 1;\n}\n\nmessage HelloReply {\n  string message = 1;\n  // message which defiened at imported package\n  google.protobuf.Timestamp reply_at = 2;\n}\n```\n\nimport `google/protobuf/timestamp.proto` and use `google.protobuf.Timestamp` message struct on above. Then, you need to defined import_path by following ways:\n\n#### define `PROTOC_IMPORT_PATHS` as global table\n\nThis package will use if `PROTOC_IMPORT_PATHS` variable is declared as global. we recommend that `init_by_lua_block` is good for you.\n\n```lua\ninit_by_lua_block {\n  PROTOC_IMPORT_PATHS = {\n    \"/usr/local/include\",\n    ...\n  }\n}\n```\n\n#### pass extra import_path to `protoc.new`\n\nIf you add more import_paths for specific package or temporarily, you can pass second or after argument on `protoc.new`.\n\n```lua\nlocal p = protoc.new(\"/etc/proto/helloworld.proto\", \"/usr/local/include\", ...)\n...\n```\n\nThese two cases will works fine. imported packages resolved automatically by following import_paths. [Example](https://github.com/ysugimoto/lua-resty-grpc-gateway/tree/master/example) also uses import statment, please check it.\n\n## CORS support\n\nThis package includes sending CORS headers for grpc-web request from other origin.\n\n```\nlocal cors = require(\"grpc-gateway.cors\")\ncors(\"http://localhost:8080\") -- or cors() to set \"*\"\n```\n\n## Polyfill grpc-web-text mode\n\nWhen you compiled protobuf with `--grpc-web_out=import_style=xxx,mode=grpcwebtext:$OUT_DIR`, grpc-web will reqeust as grpc-web-text mode.\n\nIn default, nginx can proxy only `application/grpc-web+proto` which means request body will come as binary,\nbut nginx cannot proxy `application/grpc-web-text` Content-Type because request body will come as base64-encoded string, then it cannot decode in nginx itself.\n\nSo this package also provide a tiny polyfill:\n\n```lua\nlocation / {\n  access_by_lua_block {\n    local polyfill = require(\"grpc-gateway.polyfill\")\n    polyfill()\n  }\n\n  grpc_pass localhost:9000;\n}\n```\n\nBy calling `polyfill()` , grpc-web-text mode will be succeed to proxy to backend.\n\n## Supported types and definitions\n\nlua-resty-grpc presently supports the following definitions in a given proto file. Other definitions have not been explicitly tested.\n\nThis project follows the canonical encoding from JSON to gRPC see [json-mapping](https://developers.google.com/protocol-buffers/docs/proto3#json) for a guide on how to encode your inputs for use with this plugin.\n\n### Scalar types\n* string\n* int32/64\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  string name = 1;\n  int64 age = 2;\n}\n```\n\n```\nGET /?name=test\u0026age=30\n```\n\nOR \n\n```\nPOST /\nContent-Type: application/json\n\n{\"name\":\"test\",\"age\":30}\n```\n\n### Arrays (repeated label)\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  repeated int32 grades =1;\n}\n```\nThe corresponding POST request is as follows:\n\n```\nPOST /\nContent-Type: application/json\n{\"grades\":[97,98,99]}\n```\n\n### Nested message types\n\nSince everything in gRPC is built using the `message` construct, naturally we want to be able to define several and then nest them to create more complex messages.\n\n```protobuf\nsyntax = \"proto3\";\npackage helloworld;\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  repeated ComplexMsg ex = 2;\n}\n\nmessage ComplexMsg {\n  string displayName = 1;\n  YetAnotherNestedMsg foo = 2;\n}\n```\n\nThe corresponding POST request is as follows:\n\n```\nPOST /\nContent-Type: application/json\n{\"ex\":[{\"displayName\":\"test\", \"foo\":{\"grades\":[1,2,3]}}, {\"displayName\":\"test2\",\"foo\":{\"grades\":[97,98,99]}}]}\n```\n\n\n### Enum\n```protobuf\nsyntax = \"proto3\";\n\nenum ColorType {\n    RED = 0;\n    GREEN = 1;\n    BLUE = 2;\n}\n\nmessage HelloRequest {\n  ColorType color = 1;\n}\n```\n\nNote: you can use either the enumerated value as a `String` or it's equivalent `Int`. For example: `GREEN` or `1`. If you use a value that is not defined by the enum, the lua-resty-grpc package simply ignores it. \n\nThe corresponding GET and POST requests to the gateway using an `enum` is as follows.\n\n```\nGET /?color=GREEN\n```\n\nOR\n\n```\nGET /?color=1\n```\n\n```\nPOST /\nContent-Type: application/json\n\n{\"color\":\"BLUE\"}\n```\n\nOR\n\n```\nPOST /\nContent-Type: application/json\n\n{\"color\":2}\n```\n\n## Testing using cURL\n\n\nFor quick testing of the lua-resty-grpc-gateway once it is up and running\n\nGET\n\nGiven the following proto file\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\nimport \"google/protobuf/timestamp.proto\";\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nmessage HelloRequest {\n  string displayName = 1;\n}\n\nmessage HelloReply {\n  string message = 1;\n  google.protobuf.Timestamp reply_at = 2;\n}\n```\n\n`curl -vv http://localhost:9000/rest?displayName=gRPCTest`\n\nPOST\n\nGiven the following proto file\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\nimport \"google/protobuf/timestamp.proto\";\n\nservice Greeter {\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\nenum Color {\n  RED = 0;\n  BLUE = 1;\n  GREEN = 2;\n}\n\nmessage HelloRequest {\n  string displayName = 1;\n  repeated ComplexMsg ex = 2; /** Example of nested message type**/\n  repeated string jobs = 3;\n  Color color = 4; /**Example of a enum**/\n}\n\nmessage ComplexMsg {\n  string displayName = 1;\n  YetAnotherNestedMsg foo = 2;\n}\n\nmessage YetAnotherNestedMsg {\n  repeated int32 grades = 1;\n}\n\nmessage HelloReply {\n  string message = 1;\n  google.protobuf.Timestamp reply_at = 2;\n}\n```\n\n`curl -vv -H \"Content-Type: application/json\" -d '{\"displayName\":\"grpc-rest\", \"ex\":[{\"displayName\":\"test\", \"foo\":{\"grades\":[1,2,3]}}, {\"displayName\":\"test2\",\"foo\":{\"grades\":[97,98,99]}}], \"jobs\":[\"A\",\"B\"], \"color\":\"GREEN\"}' \"http://localhost:9000/rest\"`\n\n## Known limitations\n\nThe underlying lua-protobuf library is used to encode and decode the lua tables, as such anything that this library does not support consequently this package can not support it either. \n\nOne currently known limitation is the use of annotations/options in the proto files. Specfically inside the `rpc`\n\n## License\n\nMIT\n\n## Contributors\n\n- [ysugimoto](https://github.com/ysugimoto)\n- [kgoguevgoget](https://github.com/kgoguevgoget)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysugimoto%2Flua-resty-grpc-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fysugimoto%2Flua-resty-grpc-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysugimoto%2Flua-resty-grpc-gateway/lists"}