I am trying to implement a gRPC-web call in a Vue.js application that is similar to this example (https://github.com/timostamm/protobuf-ts/blob/master/packages/example-angular-app/src/app/grpcweb-unary/grpcweb-unary.component.ts). My proto file is as follows:
syntax = "proto3";
package extractor;
service Extract {
rpc PutEntries (EntriesRequest) returns (EntriesResponse);
}
message EntriesRequest {
repeated string value = 1;
}
message EntriesResponse {
repeated string value = 1;
}
I have a gRPC server running on port 50051 with Envoy 1 on port 8080. It works when I use an external client (Kreya/BloomRPC).
The problem is that when I execute the call on the browser, I get a Uncaught (in promise) RpcError: upstream connect error or disconnect/reset before headers. reset reason: remote reset
. CORS is enabled on Envoy and everything is running in the same machine (frontend, backend and envoy).
My Vue 3 component using TS:
<script setup lang="ts">
import { ref } from "vue";
import { ExtractClient } from "./grpc/extractor.client";
import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
import type { EntriesRequest, EntriesResponse } from "./grpc/extractor";
import type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
const HOST = "http://localhost:8080";
const TIMEOUT = Date.now() + 2000;
let extracted = ref(false);
const fileInput = ref<HTMLInputElement | null>(null);
let filename = ref("");
let data = ref("");
let options: GrpcWebOptions = {
baseUrl: HOST,
timeout: TIMEOUT,
format: 'binary',
meta: {}
};
const handleFileChange = (event: Event) => {
const input = event.target as HTMLInputElement;
fileInput.value = input;
};
function extract() {
if (!fileInput.value) {
return;
}
const file = fileInput.value.files![0];
filename.value = file.name;
const reader = new FileReader();
reader.readAsText(file);
reader.onload = async () => {
const transport = new GrpcWebFetchTransport(options);
const client = new ExtractClient(transport);
const content = reader.result as string;
const entries = content.split("\n").filter((entry) => entry !== "");
// Convert the entries to the EntriesRequest format
let request: EntriesRequest = {
value: entries
}
// Make the grpc-web call to the PutEntries endpoint
let call = client.putEntries(request, options);
let response: EntriesResponse = await call.response;
data.value = response.value.join("\n");
extracted.value = true;
};
}
</script>
<template>
<form>
<input type="file" @change="handleFileChange" />
</form>
<button @click="extract">Extract</button>
<br /><br />
<span v-show="extracted">Should show data from {{ filename }}
<br />{{ data }}
</span>
</template>
The restriction I have is not change the backend that is working with gRPC and use Envoy proxy. Can anyone provide idea on what may I be doing wrong?
Note: I am using protobuf-ts with "@protobuf-ts/grpcweb-transport", since I tried grpc-web and have problems with https://github.com/grpc/grpc-web/issues/1242.
UPDATE after @Brits answer:
The application was setting I timeout when page starts, but even when I set it right before the call I get 503 Uncaught (in promise) RpcError: Service Unavailable
. I am using a less than 1kb text file, and on Kreya client it responds in 2.1 seconds.
New extract method:
function extract() {
if (!fileInput.value) {
return;
}
const file = fileInput.value.files![0];
filename.value = file.name;
const reader = new FileReader();
reader.readAsText(file);
reader.onload = async () => {
const content = reader.result as string;
const entries = content.split("\n").filter((entry) => entry !== "");
// Convert the entries to the EntriesRequest format
let request: EntriesRequest = {
value: entries
}
// Configure Grpc-Web client
let options: GrpcWebOptions = {
baseUrl: HOST,
timeout: Date.now() + 10000,
format: 'binary',
meta: {}
};
const transport = new GrpcWebFetchTransport(options);
const client = new ExtractClient(transport);
// Make the grpc-web call to the PutEntries endpoint
let call = client.putEntries(request, options);
let response: EntriesResponse = await call.response;
data.value = response.value.join("\n");
extracted.value = true;
};
}
As per the comments you are setting timeout when the page is loaded:
const TIMEOUT = Date.now() + 2000;
...
let options: GrpcWebOptions = {
baseUrl: HOST,
timeout: TIMEOUT,
format: 'binary',
meta: {}
};
This will set a deadline at 2 seconds after the page is loaded. However there is an issue here; as the docs say:
Timeout for the call in milliseconds.
If a Date object is given, it is used as a deadline.
Date.now() + 2000;
will return a number (a big number e.g. 1676577320644). If you want a Deadline use something like new Date(Date.now() + 2000);
.
As it is you are passing a very large number in as a timeout; my guess would be that this is not being handled correctly.
As you note:
the documentation states when dates are passed it is used as deadline:
So an alternative is to just specify the number of milliseconds (e.g. 2000); this should result in the call being aborted after that number of milliseconds (which is what I believe you are intending).