1
0
mirror of https://github.com/taigrr/wasm-experiments synced 2025-01-18 04:03:21 -08:00

Add experimental gRPC-Web folder

This commit is contained in:
Johan Brandhorst
2018-05-13 15:59:39 +01:00
parent 8725450750
commit d73473d0f6
606 changed files with 356893 additions and 0 deletions

7
grpc/Makefile Normal file
View File

@@ -0,0 +1,7 @@
generate:
protoc -I. ./proto/web.proto \
--go_out=plugins=grpc:$$GOPATH/src
go generate ./frontend/
serve:
go run main.go

3
grpc/backend/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Backend
This folder contains all the code used to implement the backend server.

21
grpc/backend/backend.go Normal file
View File

@@ -0,0 +1,21 @@
package backend
import (
"context"
"github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
)
// Backend should be used to implement the server interface
// exposed by the generated server proto.
type Backend struct {
}
// Ensure struct implements interface
var _ server.BackendServer = (*Backend)(nil)
func (b Backend) GetUser(ctx context.Context, req *server.GetUserRequest) (*server.User, error) {
return &server.User{
Id: "1234",
}, nil
}

268
grpc/bundle/bundle.go Normal file

File diff suppressed because one or more lines are too long

10
grpc/cert.pem Normal file
View File

@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBdjCCAR2gAwIBAgIRANfL6EMlsQylSE9cnp4W9p0wCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEChMHQWNtZSBDbzAeFw0xODAyMTAxNjI2NDNaFw0xOTAyMTAxNjI2NDNa
MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASQ
tylBafKcvUtSRiQj7LZDAnczxMOa6/OEm+NHkg01SAISzQ2oouGTgHwhlxKkapQC
zxnSw3vncl5ytWorBk8/o1QwUjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH
BH8AAAEwCgYIKoZIzj0EAwIDRwAwRAIgPkGZo6KE4dGFA1PoksmwQEb59HX560Qx
dY9FEceTSeACIHU3JRT7wEFhM2Co4QgQxuAmtTqC9zYC8zfQsvjLtpvH
-----END CERTIFICATE-----

3
grpc/frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
html/frontend.js
html/frontend.js.map
html/*.gz

14
grpc/frontend/README.md Normal file
View File

@@ -0,0 +1,14 @@
# Frontend
This folder contains the entire source for the frontend app hosted by the server.
## bundle
The `bundle` package is a `vfsgen` generated package, created from the contents of
the `html` folder. It serves as the interface that the `main.go` server uses to serve
the GopherJS frontend without the need for a `static` directory. The generation is done
via `go:generate` in `frontend.go`.
## html
The `html` folder contains the static sources used.

View File

@@ -0,0 +1,23 @@
//+build generate
package main
import (
"log"
"net/http"
"github.com/shurcooL/vfsgen"
)
func main() {
var fs http.FileSystem = http.Dir("html/")
err := vfsgen.Generate(fs, vfsgen.Options{
Filename: "bundle/bundle.go",
PackageName: "bundle",
VariableName: "Assets",
})
if err != nil {
log.Fatalln(err)
}
}

File diff suppressed because one or more lines are too long

47
grpc/frontend/frontend.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/johanbrandhorst/fetch"
)
// Build with Go WASM fork
//go:generate rm -f ./html/test.wasm
//go:generate bash -c "GOOS=js GOARCH=wasm GOROOT=$GOPATH/src/github.com/neelance/go/ $GOPATH/src/github.com/neelance/go/bin/go build -o ./html/test.wasm frontend.go"
// Integrate generated JS into a Go file for static loading.
//go:generate bash -c "go run assets_generate.go"
func main() {
c := http.Client{
Transport: &fetch.Transport{},
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Path: "/web.Backend/GetUser",
},
Header: http.Header{
"Content-Type": []string{"application/grpc-web+proto"},
},
}
//ctx, _ := context.WithTimeout(context.Background(), time.Second)
//req = req.WithContext(ctx)
resp, err := c.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}

View File

@@ -0,0 +1,30 @@
<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>
<head>
<meta charset="utf-8">
<title>Go wasm</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
async function loadAndCompile() {
let resp = await fetch("test.wasm");
let bytes = await resp.arrayBuffer();
await go.compile(bytes);
document.getElementById("runButton").disabled = false;
}
loadAndCompile();
</script>
<button onClick="console.clear(); go.run();" id="runButton" disabled>Get User</button>
</body>
</html>

BIN
grpc/frontend/html/test.wasm Executable file

Binary file not shown.

View File

@@ -0,0 +1,357 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(function () {
let args = ["js"];
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = typeof process !== "undefined";
if (isNodeJS) {
if (process.argv.length < 3) {
process.stderr.write("usage: go_js_wasm_exec [wasm binary]\n");
process.exit(1);
}
args = args.concat(process.argv.slice(3));
global.require = require;
global.fs = require("fs");
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
const now = () => {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
};
global.performance = {
timeOrigin: Date.now() - now(),
now: now,
};
const util = require("util");
global.TextEncoder = util.TextEncoder;
global.TextDecoder = util.TextDecoder;
} else {
window.global = window;
global.process = {
env: {},
exit(code) {
if (code !== 0) {
console.warn("exit code:", code);
}
},
};
let outputBuf = "";
global.fs = {
constants: {},
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
};
}
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
let mod, inst;
let values = []; // TODO: garbage collection
let resolveResume = function () { };
function mem() {
// The buffer may change when requesting more memory.
return new DataView(inst.exports.mem.buffer);
}
function setInt64(addr, v) {
mem().setUint32(addr + 0, v, true);
if (v >= 0) {
mem().setUint32(addr + 4, v / 4294967296, true);
} else {
mem().setUint32(addr + 4, -1, true); // FIXME
}
}
function getInt64(addr) {
const low = mem().getUint32(addr + 0, true);
const high = mem().getInt32(addr + 4, true);
return low + high * 4294967296;
}
function loadValue(addr) {
const id = mem().getUint32(addr, true);
return values[id];
}
function storeValue(addr, v) {
if (v === undefined) {
mem().setUint32(addr, 0, true);
return;
}
if (v === null) {
mem().setUint32(addr, 1, true);
return;
}
values.push(v);
mem().setUint32(addr, values.length - 1, true);
}
function loadSlice(addr) {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(inst.exports.mem.buffer, array, len);
}
function loadSliceOfValues(addr) {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
const id = mem().getUint32(array + i * 4, true);
a[i] = values[id];
}
return a;
}
function loadString(addr) {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(inst.exports.mem.buffer, saddr, len));
}
global.go = {
exited: false,
compileAndRun: async function (source) {
await go.compile(source);
await go.run();
},
compile: async function (source) {
mod = await WebAssembly.compile(source);
},
run: async function () {
let importObject = {
go: {
// func wasmExit(code int32)
"runtime.wasmExit": function (sp) {
go.exited = true;
process.exit(mem().getInt32(sp + 8, true));
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": function (sp) {
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = mem().getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(inst.exports.mem.buffer, p, n));
},
// func nanotime() int64
"runtime.nanotime": function (sp) {
setInt64(sp + 8, (performance.timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": function (sp) {
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func boolVal(value bool) Value
"syscall/js.boolVal": function (sp) {
storeValue(sp + 16, mem().getUint8(sp + 8) !== 0);
},
// func intVal(value int) Value
"syscall/js.intVal": function (sp) {
storeValue(sp + 16, getInt64(sp + 8));
},
// func floatVal(value float64) Value
"syscall/js.floatVal": function (sp) {
storeValue(sp + 16, mem().getFloat64(sp + 8, true));
},
// func stringVal(value string) Value
"syscall/js.stringVal": function (sp) {
storeValue(sp + 24, loadString(sp + 8));
},
// func (v Value) Get(key string) Value
"syscall/js.Value.Get": function (sp) {
storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
},
// func (v Value) set(key string, value Value)
"syscall/js.Value.set": function (sp) {
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func (v Value) Index(i int) Value
"syscall/js.Value.Index": function (sp) {
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// func (v Value) setIndex(i int, value Value)
"syscall/js.Value.setIndex": function (sp) {
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func (v Value) call(name string, args []Value) (Value, bool)
"syscall/js.Value.call": function (sp) {
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
storeValue(sp + 56, Reflect.apply(m, v, args));
mem().setUint8(sp + 60, 1);
} catch (err) {
storeValue(sp + 56, err);
mem().setUint8(sp + 60, 0);
}
},
// func (v Value) invoke(args []Value) (Value, bool)
"syscall/js.Value.invoke": function (sp) {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
storeValue(sp + 40, Reflect.apply(v, undefined, args));
mem().setUint8(sp + 44, 1);
} catch (err) {
storeValue(sp + 40, err);
mem().setUint8(sp + 44, 0);
}
},
// func (v Value) new(args []Value) (Value, bool)
"syscall/js.Value.new": function (sp) {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
storeValue(sp + 40, Reflect.construct(v, args));
mem().setUint8(sp + 44, 1);
} catch (err) {
storeValue(sp + 40, err);
mem().setUint8(sp + 44, 0);
}
},
// func (v Value) Float() float64
"syscall/js.Value.Float": function (sp) {
mem().setFloat64(sp + 16, parseFloat(loadValue(sp + 8)), true);
},
// func (v Value) Int() int
"syscall/js.Value.Int": function (sp) {
setInt64(sp + 16, parseInt(loadValue(sp + 8)));
},
// func (v Value) Bool() bool
"syscall/js.Value.Bool": function (sp) {
mem().setUint8(sp + 16, !!loadValue(sp + 8));
},
// func (v Value) Length() int
"syscall/js.Value.Length": function (sp) {
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// func (v Value) prepareString() (Value, int)
"syscall/js.Value.prepareString": function (sp) {
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// func (v Value) loadString(b []byte)
"syscall/js.Value.loadString": function (sp) {
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
"debug": function (value) {
console.log(value);
},
}
};
inst = await WebAssembly.instantiate(mod, importObject);
values = [
undefined,
null,
global,
inst.exports.mem,
function () { resolveResume(); },
];
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
let ptr = offset;
new Uint8Array(inst.exports.mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
offset += str.length + (8 - (str.length % 8));
return ptr;
};
const argc = args.length;
const argvPtrs = [];
args.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
const keys = Object.keys(process.env).sort();
argvPtrs.push(keys.length);
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${process.env[key]}`));
});
const argv = offset;
argvPtrs.forEach((ptr) => {
mem().setUint32(offset, ptr, true);
mem().setUint32(offset + 4, 0, true);
offset += 8;
});
try {
while (true) {
inst.exports.run(argc, argv);
if (go.exited) {
break;
}
await new Promise((resolve) => {
resolveResume = resolve;
});
}
} catch (err) {
console.error(err);
process.exit(1);
}
},
}
if (isNodeJS) {
go.compileAndRun(fs.readFileSync(process.argv[2])).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

5
grpc/key.pem Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKzS7jvH9hn5RZEA+OkzhWYPWWGQmRlISxSqFGWjVdiAoAoGCCqGSM49
AwEHoUQDQgAEkLcpQWnynL1LUkYkI+y2QwJ3M8TDmuvzhJvjR5INNUgCEs0NqKLh
k4B8IZcSpGqUAs8Z0sN753JecrVqKwZPPw==
-----END EC PRIVATE KEY-----

84
grpc/main.go Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2017 Johan Brandhorst. All Rights Reserved.
// See LICENSE for licensing terms.
package main
import (
"crypto/tls"
"net/http"
"path"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/lpar/gzipped"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"github.com/johanbrandhorst/wasm-experiments/grpc/backend"
"github.com/johanbrandhorst/wasm-experiments/grpc/frontend/bundle"
"github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
)
var logger *logrus.Logger
func init() {
logger = logrus.StandardLogger()
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: true,
FullTimestamp: true,
TimestampFormat: time.RFC3339Nano,
DisableSorting: true,
})
// Should only be done from init functions
grpclog.SetLoggerV2(grpclog.NewLoggerV2(logger.Out, logger.Out, logger.Out))
}
func main() {
gs := grpc.NewServer()
server.RegisterBackendServer(gs, &backend.Backend{})
wrappedServer := grpcweb.WrapServer(gs, grpcweb.WithWebsockets(true))
handler := func(resp http.ResponseWriter, req *http.Request) {
// Redirect gRPC and gRPC-Web requests to the gRPC-Web Websocket Proxy server
if req.ProtoMajor == 2 && strings.Contains(req.Header.Get("Content-Type"), "application/grpc") ||
websocket.IsWebSocketUpgrade(req) {
wrappedServer.ServeHTTP(resp, req)
} else {
// Serve the GopherJS client
folderReader(gzipped.FileServer(bundle.Assets)).ServeHTTP(resp, req)
}
}
addr := "localhost:10000"
httpsSrv := &http.Server{
Addr: addr,
Handler: http.HandlerFunc(handler),
// Some security settings
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 120 * time.Second,
TLSConfig: &tls.Config{
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
},
}
logger.Info("Serving on https://" + addr)
logger.Fatal(httpsSrv.ListenAndServeTLS("./cert.pem", "./key.pem"))
}
func folderReader(fn http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, "/") {
// Use contents of index.html for directory, if present.
req.URL.Path = path.Join(req.URL.Path, "index.html")
}
fn.ServeHTTP(w, req)
}
}

200
grpc/proto/server/web.pb.go Normal file
View File

@@ -0,0 +1,200 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/web.proto
package server // import "github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
/*
Web exposes a backend server over gRPC.
*/
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type GetUserRequest struct {
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId" json:"user_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetUserRequest) Reset() { *m = GetUserRequest{} }
func (m *GetUserRequest) String() string { return proto.CompactTextString(m) }
func (*GetUserRequest) ProtoMessage() {}
func (*GetUserRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_web_19a831568a3bf959, []int{0}
}
func (m *GetUserRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUserRequest.Unmarshal(m, b)
}
func (m *GetUserRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GetUserRequest.Marshal(b, m, deterministic)
}
func (dst *GetUserRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetUserRequest.Merge(dst, src)
}
func (m *GetUserRequest) XXX_Size() int {
return xxx_messageInfo_GetUserRequest.Size(m)
}
func (m *GetUserRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetUserRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetUserRequest proto.InternalMessageInfo
func (m *GetUserRequest) GetUserId() string {
if m != nil {
return m.UserId
}
return ""
}
type User struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) {
return fileDescriptor_web_19a831568a3bf959, []int{1}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
}
func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_User.Marshal(b, m, deterministic)
}
func (dst *User) XXX_Merge(src proto.Message) {
xxx_messageInfo_User.Merge(dst, src)
}
func (m *User) XXX_Size() int {
return xxx_messageInfo_User.Size(m)
}
func (m *User) XXX_DiscardUnknown() {
xxx_messageInfo_User.DiscardUnknown(m)
}
var xxx_messageInfo_User proto.InternalMessageInfo
func (m *User) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func init() {
proto.RegisterType((*GetUserRequest)(nil), "web.GetUserRequest")
proto.RegisterType((*User)(nil), "web.User")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Backend service
type BackendClient interface {
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
}
type backendClient struct {
cc *grpc.ClientConn
}
func NewBackendClient(cc *grpc.ClientConn) BackendClient {
return &backendClient{cc}
}
func (c *backendClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) {
out := new(User)
err := grpc.Invoke(ctx, "/web.Backend/GetUser", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Backend service
type BackendServer interface {
GetUser(context.Context, *GetUserRequest) (*User, error)
}
func RegisterBackendServer(s *grpc.Server, srv BackendServer) {
s.RegisterService(&_Backend_serviceDesc, srv)
}
func _Backend_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).GetUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/web.Backend/GetUser",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).GetUser(ctx, req.(*GetUserRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Backend_serviceDesc = grpc.ServiceDesc{
ServiceName: "web.Backend",
HandlerType: (*BackendServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetUser",
Handler: _Backend_GetUser_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/web.proto",
}
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_19a831568a3bf959) }
var fileDescriptor_web_19a831568a3bf959 = []byte{
// 197 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x28, 0xca, 0x2f,
0xc9, 0xd7, 0x2f, 0x4f, 0x4d, 0xd2, 0x03, 0xb3, 0x84, 0x98, 0xcb, 0x53, 0x93, 0x94, 0x34, 0xb9,
0xf8, 0xdc, 0x53, 0x4b, 0x42, 0x8b, 0x53, 0x8b, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84,
0xc4, 0xb9, 0xd8, 0x4b, 0x8b, 0x53, 0x8b, 0xe2, 0x33, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38,
0x83, 0xd8, 0x40, 0x5c, 0xcf, 0x14, 0x25, 0x31, 0x2e, 0x16, 0x90, 0x3a, 0x21, 0x3e, 0x2e, 0x26,
0xb8, 0x1c, 0x53, 0x66, 0x8a, 0x91, 0x19, 0x17, 0xbb, 0x53, 0x62, 0x72, 0x76, 0x6a, 0x5e, 0x8a,
0x90, 0x36, 0x17, 0x3b, 0xd4, 0x34, 0x21, 0x61, 0x3d, 0x90, 0x4d, 0xa8, 0x66, 0x4b, 0x71, 0x82,
0x05, 0x41, 0x22, 0x4a, 0x0c, 0x4e, 0xf6, 0x51, 0xb6, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a,
0xc9, 0xf9, 0xb9, 0xfa, 0x59, 0xf9, 0x19, 0x89, 0x79, 0x49, 0x45, 0x89, 0x79, 0x29, 0x19, 0xf9,
0x45, 0xc5, 0x25, 0xfa, 0xe5, 0x89, 0xc5, 0xb9, 0xba, 0xa9, 0x15, 0x05, 0xa9, 0x45, 0x99, 0xb9,
0xa9, 0x79, 0x25, 0xc5, 0xfa, 0xe9, 0x45, 0x05, 0xc9, 0xfa, 0x10, 0x3f, 0x14, 0xa7, 0x16, 0x95,
0xa5, 0x16, 0x25, 0xb1, 0x81, 0x79, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x64, 0xf1,
0x41, 0xda, 0x00, 0x00, 0x00,
}

19
grpc/proto/web.proto Normal file
View File

@@ -0,0 +1,19 @@
syntax = "proto3";
// Web exposes a backend server over gRPC.
package web;
option go_package = "github.com/johanbrandhorst/wasm-experiments/grpc/proto/server";
// Backend defines the interface exposed by the backend.
service Backend {
rpc GetUser(GetUserRequest) returns (User) {}
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
}