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

gRPC: Use grpc-wasm as client package

This commit is contained in:
Johan Brandhorst 2018-06-04 22:51:10 +01:00
parent e48ef08afd
commit 2392bc267d
No known key found for this signature in database
GPG Key ID: 266C7D9B44EAA057
19 changed files with 672 additions and 270 deletions

8
Gopkg.lock generated
View File

@ -37,6 +37,12 @@
packages = ["."]
revision = "324bdf4e09d4aa48e98660403d9def0504daaaea"
[[projects]]
branch = "master"
name = "github.com/johanbrandhorst/grpc-wasm"
packages = ["."]
revision = "d4f5643dc71d1a17cebd3df37826b34b9d3905c5"
[[projects]]
name = "github.com/lpar/gzipped"
packages = ["."]
@ -160,6 +166,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d688fec22cff0842b1870ada98bed495d65764a2c8212db1e39cf65c0d64f5b7"
inputs-digest = "b139b4b6d2de6b6210e1db3525d9b66ed479765dc05fe714d2a2f27c931794c0"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -2,6 +2,10 @@
branch = "master"
name = "github.com/johanbrandhorst/fetch"
[[constraint]]
branch = "master"
name = "github.com/johanbrandhorst/grpc-wasm"
[[constraint]]
version = "1.1.0"
name = "github.com/golang/protobuf"

View File

@ -38,3 +38,7 @@ func (b Backend) GetUser(ctx context.Context, req *server.GetUserRequest) (*serv
Id: req.GetUserId(),
}, nil
}
func (b Backend) GetUsers(req *server.GetUsersRequest, srv server.Backend_GetUsersServer) error {
return nil
}

View File

@ -1 +0,0 @@
html/test.wasm

File diff suppressed because one or more lines are too long

View File

@ -1,28 +1,14 @@
package main
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/golang/protobuf/proto"
_ "google.golang.org/genproto/googleapis/rpc/errdetails"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"github.com/johanbrandhorst/fetch"
"github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
grpc "github.com/johanbrandhorst/grpc-wasm"
server "github.com/johanbrandhorst/wasm-experiments/grpc/proto/client"
)
// Build with Go WASM fork
@ -33,220 +19,24 @@ import (
//go:generate bash -c "go run assets_generate.go"
func main() {
s := newClientConn("", "web.Backend")
req := &server.GetUserRequest{
cc, _ := grpc.Dial("")
client := server.NewBackendClient(cc)
resp, err := client.GetUser(context.Background(), &server.GetUserRequest{
UserId: "1234",
}
resp := new(server.User)
err := s.Invoke(context.Background(), "GetUser", req, resp)
})
if err != nil {
st := status.Convert(err)
fmt.Println(st.Code(), st.Message(), st.Details())
return
}
fmt.Println(resp.GetId())
req.UserId = "123"
err = s.Invoke(context.Background(), "GetUser", req, resp)
if err != nil {
st := status.Convert(err)
fmt.Println(st.Code(), st.Message(), st.Details())
return
}
fmt.Println(resp.GetId())
}
type ClientConn struct {
client *http.Client
service string
host string
}
func newClientConn(host, service string) *ClientConn {
return &ClientConn{
client: &http.Client{
Transport: &fetch.Transport{},
},
service: service,
host: host,
}
}
func (cc *ClientConn) Invoke(ctx context.Context, method string, in, out proto.Message) error {
b, err := proto.Marshal(in)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
bufHeader := make([]byte, 5)
// Write length of b into buf
binary.BigEndian.PutUint32(bufHeader[1:], uint32(len(b)))
req, err := http.NewRequest(
"POST",
strings.Join([]string{cc.host, cc.service, method}, "/"),
bytes.NewBuffer(append(bufHeader, b...)),
)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
req = req.WithContext(ctx)
addHeaders(req)
resp, err := cc.client.Do(req)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
defer resp.Body.Close()
st := statusFromHeaders(resp.Header)
if st.Code() != codes.OK {
return st.Err()
}
msgHeader := make([]byte, 5)
for {
_, err := resp.Body.Read(msgHeader)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
// 1 in MSB signifies that this is the trailer. Break loop.
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2
if msgHeader[0]>>7 == 1 {
break
}
msgLen := binary.BigEndian.Uint32(msgHeader[1:])
msg := make([]byte, msgLen)
_, err = resp.Body.Read(msg)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
err = proto.Unmarshal(msg, out)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
}
if msgHeader[0]&1 == 0 {
trailers, err := readTrailers(resp.Body)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
st = statusFromHeaders(trailers)
if st.Code() != codes.OK {
return st.Err()
}
} else {
// TODO(johanbrandhorst): Support compressed trailers
fmt.Println(resp.GetId())
}
return nil
}
func addHeaders(req *http.Request) {
// TODO: Add more headers
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http2_client.go#L356
req.Header.Add("content-type", "application/grpc-web+proto")
if dl, ok := req.Context().Deadline(); ok {
timeout := dl.Sub(time.Now())
req.Header.Add("grpc-timeout", encodeTimeout(timeout))
}
md, ok := metadata.FromOutgoingContext(req.Context())
if ok {
for h, vs := range md {
for _, v := range vs {
req.Header.Add(h, v)
}
}
resp, err = client.GetUser(context.Background(), &server.GetUserRequest{
UserId: "123",
})
if err != nil {
st := status.Convert(err)
fmt.Println(st.Code(), st.Message(), st.Details())
} else {
fmt.Println(resp.GetId())
}
}
const maxTimeoutValue int64 = 100000000 - 1
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http_util.go#L388
// div does integer division and round-up the result. Note that this is
// equivalent to (d+r-1)/r but has less chance to overflow.
func div(d, r time.Duration) int64 {
if m := d % r; m > 0 {
return int64(d/r + 1)
}
return int64(d / r)
}
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http_util.go#L398
func encodeTimeout(t time.Duration) string {
if t <= 0 {
return "0n"
}
if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "n"
}
if d := div(t, time.Microsecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "u"
}
if d := div(t, time.Millisecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "m"
}
if d := div(t, time.Second); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "S"
}
if d := div(t, time.Minute); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "M"
}
// Note that maxTimeoutValue * time.Hour > MaxInt64.
return strconv.FormatInt(div(t, time.Hour), 10) + "H"
}
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/b94ea975f3beb73799fac17cc24ee923fcd3cb5c/transport/http_util.go#L213
func decodeBinHeader(v string) ([]byte, error) {
if len(v)%4 == 0 {
// Input was padded, or padding was not necessary.
return base64.StdEncoding.DecodeString(v)
}
return base64.RawStdEncoding.DecodeString(v)
}
func readTrailers(in io.Reader) (http.Header, error) {
s := bufio.NewScanner(in)
trailers := http.Header{}
for s.Scan() {
v := s.Text()
kv := strings.SplitN(v, ": ", 2)
if len(kv) != 2 {
return nil, errors.New("malformed header: " + v)
}
trailers.Add(kv[0], kv[1])
}
return trailers, s.Err()
}
func statusFromHeaders(h http.Header) *status.Status {
details := h.Get("grpc-status-details-bin")
if details != "" {
b, err := decodeBinHeader(details)
if err != nil {
return status.New(codes.Internal, "malformed grps-status-details-bin header: "+err.Error())
}
s := &spb.Status{}
err = proto.Unmarshal(b, s)
if err != nil {
return status.New(codes.Internal, "malformed grps-status-details-bin header: "+err.Error())
}
return status.FromProto(s)
}
sh := h.Get("grpc-status")
if sh != "" {
val, err := strconv.Atoi(sh)
if err != nil {
return status.New(codes.Internal, "malformed grpc-status header: "+err.Error())
}
return status.New(codes.Code(val), h.Get("grpc-message"))
}
return status.New(codes.OK, "")
}

View File

@ -12,8 +12,8 @@ import fmt "fmt"
import math "math"
import (
grpc "github.com/johanbrandhorst/grpc-wasm"
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
@ -38,7 +38,7 @@ 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}
return fileDescriptor_web_87670d45010119fa, []int{0}
}
func (m *GetUserRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUserRequest.Unmarshal(m, b)
@ -76,7 +76,7 @@ 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}
return fileDescriptor_web_87670d45010119fa, []int{1}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
@ -103,9 +103,48 @@ func (m *User) GetId() string {
return ""
}
type GetUsersRequest struct {
NumUsers int64 `protobuf:"varint,1,opt,name=num_users,json=numUsers" json:"num_users,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetUsersRequest) Reset() { *m = GetUsersRequest{} }
func (m *GetUsersRequest) String() string { return proto.CompactTextString(m) }
func (*GetUsersRequest) ProtoMessage() {}
func (*GetUsersRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_web_87670d45010119fa, []int{2}
}
func (m *GetUsersRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUsersRequest.Unmarshal(m, b)
}
func (m *GetUsersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GetUsersRequest.Marshal(b, m, deterministic)
}
func (dst *GetUsersRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetUsersRequest.Merge(dst, src)
}
func (m *GetUsersRequest) XXX_Size() int {
return xxx_messageInfo_GetUsersRequest.Size(m)
}
func (m *GetUsersRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetUsersRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetUsersRequest proto.InternalMessageInfo
func (m *GetUsersRequest) GetNumUsers() int64 {
if m != nil {
return m.NumUsers
}
return 0
}
func init() {
proto.RegisterType((*GetUserRequest)(nil), "web.GetUserRequest")
proto.RegisterType((*User)(nil), "web.User")
proto.RegisterType((*GetUsersRequest)(nil), "web.GetUsersRequest")
}
// Reference imports to suppress errors if they are not otherwise used.
@ -121,6 +160,7 @@ const _ = grpc.SupportPackageIsVersion4
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BackendClient interface {
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
GetUsers(ctx context.Context, in *GetUsersRequest, opts ...grpc.CallOption) (Backend_GetUsersClient, error)
}
type backendClient struct {
@ -140,9 +180,42 @@ func (c *backendClient) GetUser(ctx context.Context, in *GetUserRequest, opts ..
return out, nil
}
func (c *backendClient) GetUsers(ctx context.Context, in *GetUsersRequest, opts ...grpc.CallOption) (Backend_GetUsersClient, error) {
stream, err := c.cc.NewStream(ctx, &_Backend_serviceDesc.Streams[0], "/web.Backend/GetUsers", opts...)
if err != nil {
return nil, err
}
x := &backendGetUsersClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Backend_GetUsersClient interface {
Recv() (*User, error)
grpc.ClientStream
}
type backendGetUsersClient struct {
grpc.ClientStream
}
func (x *backendGetUsersClient) Recv() (*User, error) {
m := new(User)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// BackendServer is the server API for Backend service.
type BackendServer interface {
GetUser(context.Context, *GetUserRequest) (*User, error)
GetUsers(*GetUsersRequest, Backend_GetUsersServer) error
}
func RegisterBackendServer(s *grpc.Server, srv BackendServer) {
@ -167,6 +240,27 @@ func _Backend_GetUser_Handler(srv interface{}, ctx context.Context, dec func(int
return interceptor(ctx, in, info, handler)
}
func _Backend_GetUsers_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetUsersRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BackendServer).GetUsers(m, &backendGetUsersServer{stream})
}
type Backend_GetUsersServer interface {
Send(*User) error
grpc.ServerStream
}
type backendGetUsersServer struct {
grpc.ServerStream
}
func (x *backendGetUsersServer) Send(m *User) error {
return x.ServerStream.SendMsg(m)
}
var _Backend_serviceDesc = grpc.ServiceDesc{
ServiceName: "web.Backend",
HandlerType: (*BackendServer)(nil),
@ -176,25 +270,33 @@ var _Backend_serviceDesc = grpc.ServiceDesc{
Handler: _Backend_GetUser_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "GetUsers",
Handler: _Backend_GetUsers_Handler,
ServerStreams: true,
},
},
Metadata: "proto/web.proto",
}
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_19a831568a3bf959) }
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_87670d45010119fa) }
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,
var fileDescriptor_web_87670d45010119fa = []byte{
// 236 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xc1, 0x4b, 0xc3, 0x30,
0x18, 0xc5, 0xdd, 0x26, 0xeb, 0xfa, 0x1d, 0x36, 0x88, 0xa2, 0xa2, 0x17, 0xe9, 0x49, 0x11, 0x1b,
0xd1, 0xb3, 0x08, 0xbb, 0x88, 0xd7, 0x82, 0x17, 0x2f, 0xa3, 0x69, 0x3e, 0xda, 0x2a, 0x49, 0xea,
0xf7, 0x25, 0xd6, 0x3f, 0x5f, 0x12, 0x37, 0x51, 0xf0, 0x96, 0xf7, 0xf2, 0x7b, 0x2f, 0x8f, 0xc0,
0x6a, 0x20, 0xe7, 0x9d, 0x1c, 0x51, 0x95, 0xe9, 0x24, 0x66, 0x23, 0xaa, 0xe2, 0x12, 0x96, 0x8f,
0xe8, 0x9f, 0x19, 0xa9, 0xc2, 0xf7, 0x80, 0xec, 0xc5, 0x31, 0x64, 0x81, 0x91, 0x36, 0xbd, 0x3e,
0x99, 0x9c, 0x4f, 0x2e, 0xf2, 0x6a, 0x1e, 0xe5, 0x93, 0x2e, 0x8e, 0x60, 0x3f, 0x72, 0x62, 0x09,
0xd3, 0x9f, 0xbb, 0x69, 0xaf, 0x8b, 0x12, 0x56, 0xdb, 0x0a, 0xde, 0x75, 0x9c, 0x41, 0x6e, 0x83,
0xd9, 0xc4, 0x20, 0x27, 0x72, 0x56, 0x2d, 0x6c, 0x30, 0x89, 0xb9, 0x6d, 0x21, 0x5b, 0xd7, 0xcd,
0x1b, 0x5a, 0x2d, 0xae, 0x20, 0xdb, 0x46, 0xc5, 0x41, 0x19, 0x97, 0xfd, 0xdd, 0x72, 0x9a, 0x27,
0x33, 0x3a, 0xc5, 0x9e, 0x90, 0xb0, 0xd8, 0xbd, 0x23, 0x0e, 0x7f, 0xd3, 0xfc, 0x1f, 0x7e, 0x33,
0x59, 0x3f, 0xbc, 0xdc, 0xb7, 0xbd, 0xef, 0x82, 0x2a, 0x1b, 0x67, 0xe4, 0xab, 0xeb, 0x6a, 0xab,
0xa8, 0xb6, 0xba, 0x73, 0xc4, 0x5e, 0x8e, 0x35, 0x9b, 0x6b, 0xfc, 0x1c, 0x90, 0x7a, 0x83, 0xd6,
0xb3, 0x6c, 0x69, 0x68, 0xe4, 0xf7, 0x27, 0x31, 0xd2, 0x07, 0x92, 0x9a, 0x27, 0x75, 0xf7, 0x15,
0x00, 0x00, 0xff, 0xff, 0x75, 0x28, 0x26, 0x8f, 0x3b, 0x01, 0x00, 0x00,
}

View File

@ -38,7 +38,7 @@ 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}
return fileDescriptor_web_87670d45010119fa, []int{0}
}
func (m *GetUserRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUserRequest.Unmarshal(m, b)
@ -76,7 +76,7 @@ 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}
return fileDescriptor_web_87670d45010119fa, []int{1}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
@ -103,9 +103,48 @@ func (m *User) GetId() string {
return ""
}
type GetUsersRequest struct {
NumUsers int64 `protobuf:"varint,1,opt,name=num_users,json=numUsers" json:"num_users,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetUsersRequest) Reset() { *m = GetUsersRequest{} }
func (m *GetUsersRequest) String() string { return proto.CompactTextString(m) }
func (*GetUsersRequest) ProtoMessage() {}
func (*GetUsersRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_web_87670d45010119fa, []int{2}
}
func (m *GetUsersRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUsersRequest.Unmarshal(m, b)
}
func (m *GetUsersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GetUsersRequest.Marshal(b, m, deterministic)
}
func (dst *GetUsersRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetUsersRequest.Merge(dst, src)
}
func (m *GetUsersRequest) XXX_Size() int {
return xxx_messageInfo_GetUsersRequest.Size(m)
}
func (m *GetUsersRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetUsersRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetUsersRequest proto.InternalMessageInfo
func (m *GetUsersRequest) GetNumUsers() int64 {
if m != nil {
return m.NumUsers
}
return 0
}
func init() {
proto.RegisterType((*GetUserRequest)(nil), "web.GetUserRequest")
proto.RegisterType((*User)(nil), "web.User")
proto.RegisterType((*GetUsersRequest)(nil), "web.GetUsersRequest")
}
// Reference imports to suppress errors if they are not otherwise used.
@ -121,6 +160,7 @@ const _ = grpc.SupportPackageIsVersion4
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BackendClient interface {
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
GetUsers(ctx context.Context, in *GetUsersRequest, opts ...grpc.CallOption) (Backend_GetUsersClient, error)
}
type backendClient struct {
@ -140,9 +180,42 @@ func (c *backendClient) GetUser(ctx context.Context, in *GetUserRequest, opts ..
return out, nil
}
func (c *backendClient) GetUsers(ctx context.Context, in *GetUsersRequest, opts ...grpc.CallOption) (Backend_GetUsersClient, error) {
stream, err := c.cc.NewStream(ctx, &_Backend_serviceDesc.Streams[0], "/web.Backend/GetUsers", opts...)
if err != nil {
return nil, err
}
x := &backendGetUsersClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Backend_GetUsersClient interface {
Recv() (*User, error)
grpc.ClientStream
}
type backendGetUsersClient struct {
grpc.ClientStream
}
func (x *backendGetUsersClient) Recv() (*User, error) {
m := new(User)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// BackendServer is the server API for Backend service.
type BackendServer interface {
GetUser(context.Context, *GetUserRequest) (*User, error)
GetUsers(*GetUsersRequest, Backend_GetUsersServer) error
}
func RegisterBackendServer(s *grpc.Server, srv BackendServer) {
@ -167,6 +240,27 @@ func _Backend_GetUser_Handler(srv interface{}, ctx context.Context, dec func(int
return interceptor(ctx, in, info, handler)
}
func _Backend_GetUsers_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetUsersRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BackendServer).GetUsers(m, &backendGetUsersServer{stream})
}
type Backend_GetUsersServer interface {
Send(*User) error
grpc.ServerStream
}
type backendGetUsersServer struct {
grpc.ServerStream
}
func (x *backendGetUsersServer) Send(m *User) error {
return x.ServerStream.SendMsg(m)
}
var _Backend_serviceDesc = grpc.ServiceDesc{
ServiceName: "web.Backend",
HandlerType: (*BackendServer)(nil),
@ -176,25 +270,33 @@ var _Backend_serviceDesc = grpc.ServiceDesc{
Handler: _Backend_GetUser_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "GetUsers",
Handler: _Backend_GetUsers_Handler,
ServerStreams: true,
},
},
Metadata: "proto/web.proto",
}
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_19a831568a3bf959) }
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_87670d45010119fa) }
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,
var fileDescriptor_web_87670d45010119fa = []byte{
// 236 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xc1, 0x4b, 0xc3, 0x30,
0x18, 0xc5, 0xdd, 0x26, 0xeb, 0xfa, 0x1d, 0x36, 0x88, 0xa2, 0xa2, 0x17, 0xe9, 0x49, 0x11, 0x1b,
0xd1, 0xb3, 0x08, 0xbb, 0x88, 0xd7, 0x82, 0x17, 0x2f, 0xa3, 0x69, 0x3e, 0xda, 0x2a, 0x49, 0xea,
0xf7, 0x25, 0xd6, 0x3f, 0x5f, 0x12, 0x37, 0x51, 0xf0, 0x96, 0xf7, 0xf2, 0x7b, 0x2f, 0x8f, 0xc0,
0x6a, 0x20, 0xe7, 0x9d, 0x1c, 0x51, 0x95, 0xe9, 0x24, 0x66, 0x23, 0xaa, 0xe2, 0x12, 0x96, 0x8f,
0xe8, 0x9f, 0x19, 0xa9, 0xc2, 0xf7, 0x80, 0xec, 0xc5, 0x31, 0x64, 0x81, 0x91, 0x36, 0xbd, 0x3e,
0x99, 0x9c, 0x4f, 0x2e, 0xf2, 0x6a, 0x1e, 0xe5, 0x93, 0x2e, 0x8e, 0x60, 0x3f, 0x72, 0x62, 0x09,
0xd3, 0x9f, 0xbb, 0x69, 0xaf, 0x8b, 0x12, 0x56, 0xdb, 0x0a, 0xde, 0x75, 0x9c, 0x41, 0x6e, 0x83,
0xd9, 0xc4, 0x20, 0x27, 0x72, 0x56, 0x2d, 0x6c, 0x30, 0x89, 0xb9, 0x6d, 0x21, 0x5b, 0xd7, 0xcd,
0x1b, 0x5a, 0x2d, 0xae, 0x20, 0xdb, 0x46, 0xc5, 0x41, 0x19, 0x97, 0xfd, 0xdd, 0x72, 0x9a, 0x27,
0x33, 0x3a, 0xc5, 0x9e, 0x90, 0xb0, 0xd8, 0xbd, 0x23, 0x0e, 0x7f, 0xd3, 0xfc, 0x1f, 0x7e, 0x33,
0x59, 0x3f, 0xbc, 0xdc, 0xb7, 0xbd, 0xef, 0x82, 0x2a, 0x1b, 0x67, 0xe4, 0xab, 0xeb, 0x6a, 0xab,
0xa8, 0xb6, 0xba, 0x73, 0xc4, 0x5e, 0x8e, 0x35, 0x9b, 0x6b, 0xfc, 0x1c, 0x90, 0x7a, 0x83, 0xd6,
0xb3, 0x6c, 0x69, 0x68, 0xe4, 0xf7, 0x27, 0x31, 0xd2, 0x07, 0x92, 0x9a, 0x27, 0x75, 0xf7, 0x15,
0x00, 0x00, 0xff, 0xff, 0x75, 0x28, 0x26, 0x8f, 0x3b, 0x01, 0x00, 0x00,
}

View File

@ -8,6 +8,7 @@ option go_package = "github.com/johanbrandhorst/wasm-experiments/grpc/proto/serv
// Backend defines the interface exposed by the backend.
service Backend {
rpc GetUser(GetUserRequest) returns (User) {}
rpc GetUsers(GetUsersRequest) returns (stream User) {}
}
message GetUserRequest {
@ -17,3 +18,7 @@ message GetUserRequest {
message User {
string id = 1;
}
message GetUsersRequest {
int64 num_users = 1;
}

View File

@ -0,0 +1 @@
.vscode

21
vendor/github.com/johanbrandhorst/grpc-wasm/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Johan Brandhorst
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,2 @@
# gRPC-WASM
gRPC-Web implementation in Go. Built as a drop-in alternative to google.golang.org/grpc.

View File

@ -0,0 +1,22 @@
// +build js,wasm
package grpc
// CallOption configures a Call before it starts or extracts information from
// a Call after it completes.
type CallOption interface {
// before is called before the call is sent to any server. If before
// returns a non-nil error, the RPC fails with that error.
before(*callInfo) error
// after is called after the call has completed. after cannot return an
// error, so any failures should be reported via output parameters.
after(*callInfo)
}
type callInfo struct {
}
func defaultCallInfo() *callInfo {
return &callInfo{}
}

View File

@ -0,0 +1,231 @@
// +build js,wasm
package grpc
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"errors"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/golang/protobuf/proto"
"github.com/johanbrandhorst/fetch"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type ClientConn struct {
// TODO(johanbrandhorst): Remove once we can rely on http.DefaultClient
client *http.Client
target string
}
// Dial creates a client connection to the target. The target string should
// be a URL with scheme HTTP or HTTPS, or a FQDN to infer the scheme.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
return DialContext(context.Background(), target, opts...)
}
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
return &ClientConn{
client: &http.Client{
Transport: &fetch.Transport{},
},
target: target,
}, nil
}
func (cc *ClientConn) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) {
return nil, errors.New("streaming endpoints are not yet supported")
}
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error {
b, err := proto.Marshal(args.(proto.Message))
if err != nil {
return status.Error(codes.Internal, err.Error())
}
bufHeader := make([]byte, 5)
// Write length of b into buf
binary.BigEndian.PutUint32(bufHeader[1:], uint32(len(b)))
endpoint := cc.target + "/" + method
if cc.target == "" {
endpoint = method
}
req, err := http.NewRequest(
"POST",
endpoint,
bytes.NewBuffer(append(bufHeader, b...)),
)
if err != nil {
return status.Error(codes.Unavailable, err.Error())
}
req = req.WithContext(ctx)
addHeaders(req)
resp, err := cc.client.Do(req)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
defer resp.Body.Close()
st := statusFromHeaders(resp.Header)
if st.Code() != codes.OK {
return st.Err()
}
msgHeader := make([]byte, 5)
for {
_, err := resp.Body.Read(msgHeader)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
// 1 in MSB signifies that this is the trailer. Break loop.
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2
if msgHeader[0]>>7 == 1 {
break
}
msgLen := binary.BigEndian.Uint32(msgHeader[1:])
msg := make([]byte, msgLen)
_, err = resp.Body.Read(msg)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
err = proto.Unmarshal(msg, reply.(proto.Message))
if err != nil {
return status.Error(codes.Internal, err.Error())
}
}
if msgHeader[0]&1 == 0 {
trailers, err := readTrailers(resp.Body)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
st = statusFromHeaders(trailers)
} else {
// TODO(johanbrandhorst): Support compressed trailers
}
return st.Err()
}
func addHeaders(req *http.Request) {
// TODO: Add more headers
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http2_client.go#L356
req.Header.Add("content-type", "application/grpc-web+proto")
if dl, ok := req.Context().Deadline(); ok {
timeout := dl.Sub(time.Now())
req.Header.Add("grpc-timeout", encodeTimeout(timeout))
}
md, ok := metadata.FromOutgoingContext(req.Context())
if ok {
for h, vs := range md {
for _, v := range vs {
req.Header.Add(h, v)
}
}
}
}
const maxTimeoutValue int64 = 100000000 - 1
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http_util.go#L388
// div does integer division and round-up the result. Note that this is
// equivalent to (d+r-1)/r but has less chance to overflow.
func div(d, r time.Duration) int64 {
if m := d % r; m > 0 {
return int64(d/r + 1)
}
return int64(d / r)
}
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/590da37e2dfb4705d8ebd9574ce4cb75295d9674/transport/http_util.go#L398
func encodeTimeout(t time.Duration) string {
if t <= 0 {
return "0n"
}
if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "n"
}
if d := div(t, time.Microsecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "u"
}
if d := div(t, time.Millisecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "m"
}
if d := div(t, time.Second); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "S"
}
if d := div(t, time.Minute); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "M"
}
// Note that maxTimeoutValue * time.Hour > MaxInt64.
return strconv.FormatInt(div(t, time.Hour), 10) + "H"
}
// Copied from grpc-go
// https://github.com/grpc/grpc-go/blob/b94ea975f3beb73799fac17cc24ee923fcd3cb5c/transport/http_util.go#L213
func decodeBinHeader(v string) ([]byte, error) {
if len(v)%4 == 0 {
// Input was padded, or padding was not necessary.
return base64.StdEncoding.DecodeString(v)
}
return base64.RawStdEncoding.DecodeString(v)
}
func readTrailers(in io.Reader) (http.Header, error) {
s := bufio.NewScanner(in)
trailers := http.Header{}
for s.Scan() {
v := s.Text()
kv := strings.SplitN(v, ": ", 2)
if len(kv) != 2 {
return nil, errors.New("malformed header: " + v)
}
trailers.Add(kv[0], kv[1])
}
return trailers, s.Err()
}
func statusFromHeaders(h http.Header) *status.Status {
details := h.Get("grpc-status-details-bin")
if details != "" {
b, err := decodeBinHeader(details)
if err != nil {
return status.New(codes.Internal, "malformed grps-status-details-bin header: "+err.Error())
}
s := &spb.Status{}
err = proto.Unmarshal(b, s)
if err != nil {
return status.New(codes.Internal, "malformed grps-status-details-bin header: "+err.Error())
}
return status.FromProto(s)
}
sh := h.Get("grpc-status")
if sh != "" {
val, err := strconv.Atoi(sh)
if err != nil {
return status.New(codes.Internal, "malformed grpc-status header: "+err.Error())
}
return status.New(codes.Code(val), h.Get("grpc-message"))
}
return status.New(codes.OK, "")
}

View File

@ -0,0 +1,51 @@
// +build js,wasm
package grpc
import (
"context"
"google.golang.org/grpc"
)
// ClientStream defines the interface a client stream has to satisfy.
type ClientStream grpc.ClientStream
// ServerStream defines the interface a server stream has to satisfy.
type ServerStream grpc.ServerStream
// StreamHandler defines the handler called by gRPC server to complete the
// execution of a streaming RPC. If a StreamHandler returns an error, it
// should be produced by the status package, or else gRPC will use
// codes.Unknown as the status code and err.Error() as the status message
// of the RPC.
type StreamHandler func(srv interface{}, stream ServerStream) error
// StreamDesc represents a streaming RPC service's method specification.
type StreamDesc struct {
StreamName string
Handler StreamHandler
// At least one of these is true.
ServerStreams bool
ClientStreams bool
}
type methodHandler func(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor UnaryServerInterceptor) (interface{}, error)
// MethodDesc represents an RPC service's method specification.
type MethodDesc struct {
MethodName string
Handler methodHandler
}
// ServiceDesc represents an RPC service's specification.
type ServiceDesc struct {
ServiceName string
// The pointer to the service interface. Used to check whether the user
// provided implementation satisfies the interface requirements.
HandlerType interface{}
Methods []MethodDesc
Streams []StreamDesc
Metadata interface{}
}

View File

@ -0,0 +1,11 @@
// +build js,wasm
package grpc
// DialOption configures how we set up the connection.
type DialOption func(*dialOptions)
// dialOptions configure a Dial call. dialOptions are set by the DialOption
// values passed to Dial.
type dialOptions struct {
}

17
vendor/github.com/johanbrandhorst/grpc-wasm/doc.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
// +build js,wasm
package grpc
// The SupportPackageIsVersion variables are referenced from generated protocol
// buffer files to ensure compatibility with the gRPC version used. The latest
// support package version is 4.
//
// Older versions are kept for compatibility. They may be removed if
// compatibility cannot be maintained.
//
// These constants should not be referenced from any other code.
//
// Note: grpc-wasm is compatible with Version4+ files only.
const (
SupportPackageIsVersion4 = true
)

View File

@ -0,0 +1,26 @@
// +build js,wasm
package grpc
import "context"
// UnaryServerInfo consists of various information about a unary RPC on
// server side. All per-rpc information may be mutated by the interceptor.
type UnaryServerInfo struct {
// Server is the service implementation the user provides. This is read-only.
Server interface{}
// FullMethod is the full RPC method string, i.e., /package.service/method.
FullMethod string
}
// UnaryHandler defines the handler invoked by UnaryServerInterceptor to complete the normal
// execution of a unary RPC. If a UnaryHandler returns an error, it should be produced by the
// status package, or else gRPC will use codes.Unknown as the status code and err.Error() as
// the status message of the RPC.
type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)
// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info
// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper
// of the service method implementation. It is the responsibility of the interceptor to invoke handler
// to complete the RPC.
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

View File

@ -0,0 +1,8 @@
// +build js,wasm
package grpc
// Server is a gRPC server to serve RPC requests.
type Server struct{}
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {}