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
19 changed files with 672 additions and 270 deletions

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{}) {}