grpc-web
gRPC is a high-performance RPC framework based on HTTP/2 and Protocol Buffers, but it is not natively supported by browsers. gRPC-Web defines a browser-compatible protocol for sending gRPC requests over HTTP/1.1 or HTTP/2.
The grpc-web plugin translates gRPC-Web requests into native gRPC calls and forwards them to upstream gRPC services.
Request Handling
The grpc-web plugin processes client requests with specific HTTP methods, content types, and CORS rules.
Supported HTTP Methods
The plugin supports:
POSTfor gRPC-Web requestsOPTIONSfor CORS preflight checks
See CORS support for details.
Supported Content Types
The plugin recognizes the following content types:
application/grpc-webapplication/grpc-web-textapplication/grpc-web+protoapplication/grpc-web-text+proto
It automatically decodes messages in binary or base64 text format and translates them into standard gRPC for the upstream server. See Protocol differences vs gRPC over HTTP2 for more details.
CORS Handling
The plugin automatically handles cross-origin requests. By default:
- All origins (
*) are allowed POSTrequests are permitted- Accepted request headers:
content-type,x-grpc-web,x-user-agent - Exposed response headers:
grpc-status,grpc-message
Examples
The following examples demonstrate how to configure and use the grpc-web plugin with a gRPC-Web client.
Prerequisites
Before proceeding with the examples, complete the following preliminary steps to set up an upstream server and gRPC-Web client.
Start an Upstream Server
Start a grpcbin server to serve as the example upstream:
- Docker
- Kubernetes
docker run -d \
--name grpcbin \
-p 9000:9000 \
moul/grpcbin
Create a Kubernetes manifest file for the deployment of the grpcbin server:
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: grpcbin
spec:
replicas: 1
selector:
matchLabels:
app: grpcbin
template:
metadata:
labels:
app: grpcbin
spec:
containers:
- name: grpcbin
image: moul/grpcbin
ports:
- containerPort: 9000
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: grpcbin
spec:
selector:
app: grpcbin
ports:
- protocol: TCP
port: 9000
targetPort: 9000
type: ClusterIP
Apply the configuration to your cluster:
kubectl apply -f grpcbin.yaml
Generate gRPC-Web client code
Download the protocol buffer definition hello.proto:
curl -O https://raw.githubusercontent.com/moul/pb/refs/heads/master/hello/hello.proto
Install protobuf and protoc-gen-grpc-web.
Generate the gRPC-Web client code from hello.proto:
protoc \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:. \
hello.proto
You should see two files generated in the current directory: hello_pb.js for protocol buffers message classes and hello_grpc_web_pb.js for gRPC-Web client stubs.
Create a Client
Create a Node.js project and install the required dependencies:
npm init -y
npm install xhr2 grpc-web google-protobuf
Create a client file:
const XMLHttpRequest = require('xhr2');
const { HelloServiceClient } = require('./hello_grpc_web_pb');
const { HelloRequest } = require('./hello_pb');
global.XMLHttpRequest = XMLHttpRequest;
function sayHello(){
const client = new HelloServiceClient('http://127.0.0.1:9080/grpc/web', null, {
format: 'text',
});
const req = new HelloRequest();
req.setGreeting('jack');
const call = client.sayHello(req, {}, (err, resp) => {
if (err) {
console.error('grpc error:', err.code, err.message);
} else {
console.log('reply:', resp.getReply());
}
});
call.on('metadata', (metadata) => {
console.log('Response headers:', metadata);
});
}
function lotsOfReplies() {
const client = new HelloServiceClient('http://127.0.0.1:9080/grpc/web', null, {
format: 'text',
});
const req = new HelloRequest();
req.setGreeting('rep');
const stream = client.lotsOfReplies(req, {});
stream.on('metadata', (metadata) => {
console.log('Response headers:', metadata);
});
}
lotsOfReplies()
sayHello()
You can later run the client with node client.js to send both unary and server-streaming requests to your gRPC server via the gateway.
Proxy gRPC-Web (Prefix Match Route)
The following examples demonstrate how to configure and use the grpc-web plugin with the gRPC-Web client set up previously.
Create a route with the grpc-web plugin as such:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "grpc-web-route",
"uri": "/grpc/web/*",
"plugins": {
"grpc-web": {}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"192.168.10.103:9000": 1
}
}
}'
❶ Configure the uri to prefix-match the route requested in client.js.
❷ Enable the grpc-web plugin.
❸ Set the upstream scheme to grpc.
❹ Replace with your upstream server address.
services:
- name: grpcbin
routes:
- name: grpc-web-route
uris:
- /grpc/web/*
plugins:
grpc-web: {}
upstream:
scheme: grpc
type: roundrobin
nodes:
- host: grpcbin
port: 9000
weight: 1
❶ Configure the uri to prefix-match the route requested in client.js.
❷ Enable the grpc-web plugin.
❸ Set the upstream scheme to grpc.
❹ Replace with your upstream server address.
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
The Gateway API GRPCRoute matches requests by gRPC service and method names, not by HTTP path prefix. To proxy gRPC-Web traffic with the grpc-web plugin, configure a GRPCRoute with no method matches so it accepts all incoming gRPC requests. When using this configuration, update the HelloServiceClient base URL in client.js to http://127.0.0.1:9080 (without the /grpc/web prefix).
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: grpc-web-plugin-config
spec:
plugins:
- name: grpc-web
config: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
namespace: aic
name: grpc-web-route
spec:
parentRefs:
- name: apisix
rules:
- filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: grpc-web-plugin-config
backendRefs:
- name: grpcbin
port: 9000
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: grpcbin
spec:
ingressClassName: apisix
scheme: grpc
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: grpc-web-route
spec:
ingressClassName: apisix
http:
- name: grpc-web-route
match:
paths:
- /grpc/web/*
backends:
- serviceName: grpcbin
servicePort: 9000
plugins:
- name: grpc-web
enable: true
config: {}
❶ Set the upstream scheme to grpc.
❷ Configure the route path to prefix-match the route requested in client.js.
❸ Replace with your upstream service name and port.
❹ Enable the grpc-web plugin.
Apply the configuration to your cluster:
kubectl apply -f grpc-web-ic.yaml
In APISIX versions prior to 3.15.0 and API7 Enterprise versions prior to 3.8.21, the route URI must use a prefix match because gRPC-Web clients include the package name, service name, and method name in the request URI. Using an absolute URI match in these versions will prevent the request from matching the route. Absolute URI routes are supported in later versions.
In this example, the route URI must be configured as /grpc/web/* to correctly match client requests such as /grpc/web/hello.HelloService/SayHello. Using a broader prefix like /grpc/* would prevent the gateway from correctly extracting the full service path, resulting in errors such as unknown service web/hello.HelloService.
Run the client to send requests to the gateway route:
node client.js
You should see a reply from the upstream gRPC server:
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
reply: hello jack
Proxy gRPC-Web (Absolute URI)
This example applies to APISIX 3.15.0 and later, and API7 Enterprise 3.8.21 and later.
When an absolute URI is used, the gateway does not automatically strip the URI path prefix. To forward requests correctly to the upstream gRPC server, use the proxy-rewrite plugin to adjust the request path.
Create a route with the grpc-web plugin as such:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "grpc-web-route",
"uri": "/grpc/web/hello.HelloService/SayHello",
"plugins": {
"grpc-web": {},
"proxy-rewrite": {
"uri": "/hello.HelloService/SayHello",
"set_ngx_uri": "true"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"192.168.10.103:9000": 1
}
}
}'
❶ Configure the uri to use the absolute path, including the base prefix and the gRPC full service path.
❷ Configure proxy-rewrite to rewrite the path and strip the route prefix.
❸ Set set_ngx_uri to true to update the requested path to the URI defined in the proxy-rewrite plugin. Without this setting, the gateway will not correctly forward the request to the upstream, resulting in errors such as unknown service grpc/web/hello.HelloService.
❹ Set the upstream scheme to grpc.
❺ Replace with your upstream server address.
services:
- name: grpcbin
routes:
- name: grpc-web-route
uris:
- /grpc/web/hello.HelloService/SayHello
plugins:
grpc-web: {}
proxy-rewrite:
uri: /hello.HelloService/SayHello
set_ngx_uri: "true"
upstream:
scheme: grpc
type: roundrobin
nodes:
- host: grpcbin
port: 9000
weight: 1
❶ Configure the uri to use the absolute path, including the base prefix and the gRPC full service path.
❷ Configure proxy-rewrite to rewrite the path and strip the route prefix.
❸ Set set_ngx_uri to true to update the requested path to the URI defined in the proxy-rewrite plugin. Without this setting, the gateway will not correctly forward the request to the upstream, resulting in errors such as unknown service grpc/web/hello.HelloService.
❹ Set the upstream scheme to grpc.
❺ Replace with your upstream server address.
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
The Gateway API GRPCRoute matches requests by gRPC service and method names, not by HTTP path prefix. A GRPCRoute with service: hello.HelloService and method: SayHello creates an exact route at /hello.HelloService/SayHello. No proxy-rewrite is needed because the route path already matches the gRPC method path. When using this configuration, update the HelloServiceClient base URL in client.js to http://127.0.0.1:9080 (without the /grpc/web prefix).
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: grpc-web-absolute-plugin-config
spec:
plugins:
- name: grpc-web
config: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
namespace: aic
name: grpc-web-absolute-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- method:
service: hello.HelloService
method: SayHello
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: grpc-web-absolute-plugin-config
backendRefs:
- name: grpcbin
port: 9000
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: grpcbin
spec:
ingressClassName: apisix
scheme: grpc
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: grpc-web-absolute-route
spec:
ingressClassName: apisix
http:
- name: grpc-web-absolute-route
match:
paths:
- /grpc/web/hello.HelloService/SayHello
backends:
- serviceName: grpcbin
servicePort: 9000
plugins:
- name: grpc-web
enable: true
config: {}
- name: proxy-rewrite
enable: true
config:
uri: /hello.HelloService/SayHello
set_ngx_uri: "true"
❶ Set the upstream scheme to grpc.
❷ Configure the route path to use the absolute URI, including the base prefix and the gRPC full service path.
❸ Replace with your upstream service name and port.
❹ Configure proxy-rewrite to rewrite the path and strip the route prefix.
❺ Set set_ngx_uri to true to update the requested path to the URI defined in the proxy-rewrite plugin. Without this setting, the gateway will not correctly forward the request to the upstream, resulting in errors such as unknown service grpc/web/hello.HelloService.
Apply the configuration to your cluster:
kubectl apply -f grpc-web-absolute-ic.yaml
Run the client to send requests to the gateway route:
node client.js
You should see a reply from the upstream gRPC server:
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
reply: hello jack