http: body field path 'foo' must be a non-repeated message.' - google-cloud-endpoints

I'm trying to setup transcoding HTTP/JSON to gRPC with the gRPC helloworld example on gcloud endpoints. My annotation to the helloworld.proto file is:
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/hello"
body: "name"
};
}
}
and my service config is:
http:
rules:
- selector: helloworld.Greeter.SayHello
post: /v1/hello
body: name
After generating the api_descriptor.pb file, I execute:
gcloud endpoints services deploy api_descriptor.pb api_config.yaml
and I get:
ERROR: (gcloud.endpoints.services.deploy) INVALID_ARGUMENT: Cannot convert to service config.
'ERROR: helloworld/helloworld.proto:43:3: http: body field path 'foo' must be a non-repeated message.'
Any help would be greatly appreciated. :)

Apparently, the body cannot be a base type. Wrapping the name in a message seems to work:
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/hello"
body: "person"
};
}
}
message Person {
string name = 1;
}
// The request message containing the user's name.
message HelloRequest {
Person person = 1;
}

Related

oak on Deno - How do I build a URL to a route?

I come from a land of ASP.NET Core. Having fun learning a completely new stack.
I'm used to being able to:
name a route "orders"
give it a path like /customer-orders/{id}
register it
use the routing system to build a URL for my named route
An example of (4) might be to pass a routeName and then routeValues which is an object like { id = 193, x = "y" } and the routing system can figure out the URL /customer-orders/193?x=y - notice how it just appends extraneous key-vals as params.
Can I do something like this in oak on Deno?? Thanks.
Update: I am looking into some functions on the underlying regexp tool the routing system uses. It doesn't seem right that this often used feature should be so hard/undiscoverable/inaccessible.
https://github.com/pillarjs/path-to-regexp#compile-reverse-path-to-regexp
I'm not exactly sure what you mean by "building" a URL, but the URL associated to the incoming request is defined by the requesting client, and is available in each middleware callback function's context parameter at context.request.url as an instance of the URL class.
The documentation provides some examples of using a router and the middleware callback functions that are associated to routes in Oak.
Here's an example module which demonstrates accessing the URL-related data in a request:
so-74635313.ts:
import { Application, Router } from "https://deno.land/x/oak#v11.1.0/mod.ts";
const router = new Router({ prefix: "/customer-orders" });
router.get("/:id", async (ctx, next) => {
// An instance of the URL class:
const { url } = ctx.request;
// An instance of the URLSearchParams class:
const { searchParams } = url;
// A string:
const { id } = ctx.params;
const serializableObject = {
id,
// Iterate all the [key, value] entries and collect into an array:
searchParams: [...searchParams.entries()],
// A string representation of the full request URL:
url: url.href,
};
// Respond with the object as JSON data:
ctx.response.body = serializableObject;
ctx.response.type = "application/json";
// Log the object to the console:
console.log(serializableObject);
await next();
});
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
function printStartupMessage({ hostname, port, secure }: {
hostname: string;
port: number;
secure?: boolean;
}): void {
if (!hostname || hostname === "0.0.0.0") hostname = "localhost";
const address =
new URL(`http${secure ? "s" : ""}://${hostname}:${port}/`).href;
console.log(`Listening at ${address}`);
console.log("Use ctrl+c to stop");
}
app.addEventListener("listen", printStartupMessage);
await app.listen({ port: 8000 });
In a terminal shell (I'll call it shell A), the program is started:
% deno run --allow-net so-74635313.ts
Listening at http://localhost:8000/
Use ctrl+c to stop
Then, in another shell (I'll call it shell B), a network request is sent to the server at the route described in your question — and the response body (JSON text) is printed below the command:
% curl 'http://localhost:8000/customer-orders/193?x=y'
{"id":"193","searchParams":[["x","y"]],"url":"http://localhost:8000/customer-orders/193?x=y"}
Back in shell A, the output of the console.log statement can be seen:
{
id: "193",
searchParams: [ [ "x", "y" ] ],
url: "http://localhost:8000/customer-orders/193?x=y"
}
ctrl + c is used to send an interrupt signal (SIGINT) to the deno process and stop the server.
I am fortunately working with a React developer today!
Between us, we've found the .url(routeName, ...) method on the Router instance and that does exactly what I need!
Here's the help for it:
/** Generate a URL pathname for a named route, interpolating the optional
* params provided. Also accepts an optional set of options. */
Here's it in use in context:
export const routes = new Router()
.get(
"get-test",
"/test",
handleGetTest,
);
function handleGetTest(context: Context) {
console.log(`The URL for the test route is: ${routes.url("get-test")}`);
}
// The URL for the test route is: /test

Unexpected end of JSON input in my Deno HTTP server code

I'm actually dealing with some issues.
In fact I'm coding an HTTP server with Deno and I have the same problem that I did with Node.js.
I use Postman to simulate POST request to my server.
When I do a Request with the URL of my localhost the server is working:
The deno server code :
const listener = Deno.listen({ port: 8000 });
console.log(`Server is running on http://localhost:8000`);
for await (const conn of listener) {
handleNewConnection(conn);
}
async function handleNewConnection(conn: Deno.Conn) {
for await (const req of Deno.serveHttp(conn)) {
await handleRequest(req.request, req.respondWith);
}
}
async function handleRequest(req: Request, resp: any) {
if (req.method === "POST") {
let body = await req.json();
console.log(body);
resp(new Response(null));
} else {
console.log(req.method)
resp(new Response(null));
}
}
The request that I'm sending in localhost:8000:
What I receive in console.log :
{
name: "Molecule Man",
age: 29,
secretIdentity: "Dan Jukes",
powers: [ "Radiation resistance", "Turning tiny", "Radiation blast" ]
}
Then I'm gonna show you when I use a Cloudflare tunnel.
This Cloudflare tunnel is directly sending the requests to my server on port 8000.
The POST request that I'm sending :
What I receive with the console.log() :
error: Uncaught (in promise) SyntaxError: Unexpected end of JSON input
let body = await req.json();
^
at parse (<anonymous>)
at packageData (deno:ext/fetch/22_body.js:328:16)
at Request.json (deno:ext/fetch/22_body.js:267:18)
at async handleRequest (file:///C:/Users/Utilisateur/Projets/poc-pdu/HTTP-serv-deno/http-serv-deno.ts:17:16)
at async handleNewConnection (file:///C:/Users/Utilisateur/Projets/poc-pdu/HTTP-serv-deno/http-serv-deno.ts:10:5)
That's the error, in my opinion, this is due to the time it takes for the request to arrive at it's destination.
I thank all the people who will take the time to respond to this post.
Bye!

How can I download a .zip file over a http to grpc transcoded endpoint?

I have the grpc endpoint defined as below. The grpc endpoint returns a .zip file. It works OK over a grpc channel, but I'm having issues downloading it over the REST endpoint.
I use envoy to do the http transcoding.
My problem right now, on the REST endpoint, is that the download response header is always set to application/json instead of application/zip for the zip download to properly work.
Any idea how I can instruct envoy to set the proper headers during transcoding, so that the REST download will properly work ?
// Download build
//
// Download build
rpc DownloadBuild(DownloadBuildRequest) returns (stream DownloadResponse) {
option (google.api.http) = {
get : "/v4/projects/{projectId}/types/{buildType}/builds/{buildVersion}/.download"
headers: {}
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
description: "Download build.";
summary: "Download build.";
tags: "Builds";
produces: "application/zip";
responses: {
key: "200"
value: {
description: "Download build";
}
}
responses: {
key: "401"
value: {
description: "Request could not be authorized";
}
}
responses: {
key: "404"
value: {
description: "Build not found";
}
}
responses: {
key: "500"
value: {
description: "Internal server error";
}
}
};
}
Ok I've found a way to partially solve my problem. Basically I will have to use google.api.HttpBody as response and set the content type in there.
https://github.com/googleapis/googleapis/blob/master/google/api/httpbody.proto
https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_json_transcoder_filter#sending-arbitrary-content
I still have a problem with setting the downloaded file name and extension header.
To set the filename header, I had to define a server interceptor, similar to what is mentioned here:
How to pass data from grpc rpc call to server interceptor in java
private class TrailerCall<ReqT, RespT> extends SimpleForwardingServerCall<ReqT, RespT> {
public TrailerCall(final ServerCall<ReqT, RespT> delegate) {
super(delegate);
}
#Override
public void sendHeaders(Metadata headers) {
headers.merge(RESPONSE_HEADERS_HOLDER_KEY.get());
super.sendHeaders(headers);
}
}

Can't send a request using grpcurl

I have a server using NestJs+gRPC, I storage data in PostgreSQL, there is no problems in getting data and so on. I can't send grpcurl request :((
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>(grpcClientOptions);
await app.startAllMicroservicesAsync();
await app.listen(3000);
console.log(`Application is running on: ${await app.getUrl()}`);
}
(async () => await bootstrap())();
export const grpcClientOptions: ClientOptions = {
transport: Transport.GRPC,
options: {
url: '0.0.0.0:5000',
package: 'user',
protoPath: join(__dirname,'user/user.proto'),
loader: {
keepCase: true,
longs: Number,
defaults: false,
arrays: true,
objects: true,
},
},
};
Proto file looks like
syntax = "proto3";
package user;
service UserService {
rpc FindOne (UserById) returns (User) {}
}
message UserById {
string id = 1;
}
message User {
int32 id = 1;
string name = 2;
string password = 3;
string email = 4;
string createdAt = 5;
string updatedAt = 6;
}
And user controller
#Get(':id')
getById(#Param('id') id: string): Observable<User> {
console.log(id);
return this.userService.findOne({ id : +id });
}
#GrpcMethod('UserService','FindOne')
async findOne(data: UserById): Promise<User> {
const { id } = data;
console.log(id);
return this.userModel.findOne({
where: {
id : id
},
});
}
It works correctly when I sending request from browser, but I can't make it using grpcurl.
enter image description here
Thanks in forward!
I suspect (!) the issue is that you're on Windows but using a forward-slash (/) between src/user whereas on Windows (!?) file path separators should be a back-slash (\).
please see this link on Microsoft Test gRPC services with gRPCurl and try -d '{\"id\": 1}'.
Grpcurl on winodws differes from Linux/Mac.
On winodws, no need to enclose a json message single quote.
E.g.
grpcurl.exe --plaintext -d {\"message\":\"How\u0020are\u0020you\"} localhost:9090 GreetingService.greeting
Note: We need to escape whitespace using \u0020 and escape double quotes with a back slash'\'.

Envoy routing based on gRPC method and paremeter

I have a Protobuf file Routing.proto used in a gRPC service. I also have an Envoy simple.yaml file.
I am trying to gRPC route match on method service_B_hello(1) where 1 is a camera_id value wrapped inside CopyImageRequest.
How can i do this route matching in Envoy with method parameter camera_id within the request ?
Routing.proto:
syntax = "proto3";
package routing;
message CopyImageRequest {
int32 camera_id = 1;
}
message CopyImageResponse {
string result = 1;
}
service RoutingService {
rpc service_A_hello(CopyImageRequest) returns (CopyImageResponse) {};
rpc service_B_hello(CopyImageRequest) returns (CopyImageResponse) {};
rpc service_C_hello(CopyImageRequest) returns (CopyImageResponse) {};
}
simple.yaml Envoy file:
routes:
- match:
prefix:"/routing.RoutingService/service_B_hello"
headers:
- name: "method"
exact_match: "service_B_hello"
- name: "camera_id"
regex_match: ^[0-3]$
- name: content-type
exact_match: application/grpc
grpc: {}
route:
auto_host_rewrite: true
cluster: grpc_stateless_service
TIA,

Resources