How can I run graphql subscription locally without network transport? - go

I am working on implementing graphql in AWS lambda + apigateway in golang. The API gateway is a websocket gateway which works as network layer. Lambda is just golang application to handle graphql query and resolvers.
I am using github.com/graph-gophers/graphql-go as the graphql library and I need to support subscription. I'd like to avoid importing any network layer in my golang code since all of that is handled by api gateway. My lambda should be just a pure graphql application.
The code I have is to load the schema first:
graphqlSchema = graphql.MustParseSchema(s, &resolver.Resolver{})
h = handler.New(graphqlSchema)
In the handler, it works like a wrapper of graphql. It passes query parameters to schema.Exec method call as below:
type Handler struct {
schema *graphql.Schema
}
func New(schema *graphql.Schema) *Handler {
h := Handler{schema}
return &h
}
func (h *Handler) Exec(ctx context.Context, operationName string, query string, variables map[string]interface{}) *graphql.Response {
response := h.schema.Exec(ctx, query, operationName, variables)
return response
}
when I run the code, I got this error from the response of h.schema.Exec:
&{[graphql: graphql-ws protocol header is missing] [] map[]}
it seems it complains about missing graphql-ws protocol. Is there a way to make it work without any protocol/network dependency? I'd like to make it work just like in local memory.

Related

How to detect a cancelled/lost/closed connection in Cloud Run with gRPC server streaming?

I have a server-side streaming RPC hosted on Google Cloud Run.
With the following proto definition:
syntax = "proto3";
package test.v1;
service MyService {
// Subscribe to a stream of events.
rpc Subscribe (SubscribeRequest) returns (stream SubscribeResponse) {}
}
message SubscribeRequest {
}
message SubscribeResponse {
}
Using BloomRPC/grpcurl, when I stop the method I get a stream.Context().Done() event which I can use to gracefully stop certain tasks. Here is an example of the Suscribe method:
func (s *myService) Subscribe(req *pb.SubscribeRequest, stream pb.Instruments_SubscribeServer) error {
// Create a channel for this client.
ch := make(chan *pb.SubscribeResponse)
// Add the channel object 'ch' to a Global list of channels where we have a 'broadcaster' sending
// messages to all connected clients.
// TODO: pass to broadcaster client list.
for {
select {
case <-stream.Context().Done():
close(ch)
fmt.Println("Removed client from global list of channels")
return nil
case res := <-ch:
_ = stream.Send(res)
}
}
}
On the client side, when I test the service locally (i.e. running a local gRPC server in Golang), using BloomRPC/grpcurl I get a message on the stream.Context().Done() channel whenever I stop the BloomRPC/grpcurl connection. This is the expected behaviour.
However, running the exact same code on Cloud Run in the same way (via BloomRPC/grpcurl), I don't get a stream.Context().Done() message - any reason why this would be different on Google Cloud Run? Looking at the Cloud Run logs, a call to the Subscribe method essentially 'hangs' until the request reaches its timeout.
I needed to enable HTTP/2 Connections in Cloud Run for this to work.

Gin send metrics to promethesus where the URL has a parameter in the path

I have a gin service where one of the endpoint looks like this:
const myPath= "/upload-some-file/:uuid"
In my middleware that sends data to prometheus, I have something like this:
requestCounter = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "all-http-requests",
Help: "Total number of http requests",
}, []string{"Method", "Endpoint"})
func Telemetry() gin.HandlerFunc {
return func(c *gin.Context) {
// Metrics for requests volume
requestCounter.With(prometheus.Labels{"Method": c.Request.Method, "Endpoint": c.Request.URL.Path}).Inc()
c.Next()
}
}
But I notice that that prometheus is unable to figure out that a parameter is actually embedded into the path, therefore it treats every unique uuid as a new path.
Is there some way to let prometheus realize that it is actually using a URL with embedded parameters?
I found this https://github.com/gin-gonic/gin/issues/748#issuecomment-543683781
So I can simply do c.FullPath() to get the matched route.

How can I set HTTP request headers when using Go-Github and an http.Transport?

I am writing an app that uses the GitHub API to look at repositories in my GitHub orgs. I am using the github.com/google/go-github library.
I am also using the github.com/gregjones/httpcache so that I can do token based authentication as well as set the conditional headers for the API calls. I have got authentication working thus:
ctx := context.Background()
// GitHUb API authentication
transport = &oauth2.Transport{
Source: oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: gh.tokens.GitHub.Token,
},
),
}
// Configure HTTP memory caching
transport = &httpcache.Transport{
Transport: transport,
Cache: httpcache.NewMemoryCache(),
MarkCachedResponses: true,
}
// Create the http client that GutHUb will use
httpClient := &http.Client{
Transport: transport,
}
// Attempt to login to GitHub
client := github.NewClient(httpClient)
However I am unable to work out how to add the necessary If-Match header when I use client.Repositories.Get for example. This is so I can work out if the repo has changed in the last 24 hours for exampple.
I have searched how to do this, but the examples I come across show how to create an HTTP client and then create a request (so the headers can be added) and then do a Do action on it. However As I am using the client directly I do not have that option.
The documentation for go-github states that for conditional requests:
The GitHub API has good support for conditional requests which will help prevent you from burning through your rate limit, as well as help speed up your application. go-github does not handle conditional requests directly, but is instead designed to work with a caching http.Transport. We recommend using https://github.com/gregjones/httpcache for that.
Learn more about GitHub conditional requests at https://developer.github.com/v3/#conditional-requests.
I do not know how to add it in my code, any help is greatly appreciated.
As tends to be the case with these things, shortly after posting my question I found the answer.
The trick is to set the headers using the Base in the Oauth2 transport thus:
transport = &oauth2.Transport{
Source: oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: gh.tokens.GitHub.Token,
},
),
Base: &transportHeaders{
modifiedSince: modifiedSince,
},
}
The struct and method look like:
type transportHeaders struct {
modifiedSince string
}
func (t *transportHeaders) RoundTrip(req *http.Request) (*http.Response, error) {
// Determine the last modified date based on the transportHeader options
// Do not add any headers if blank or zero
if t.modifiedSince != "" {
req.Header.Set("If-Modified-Since", t.modifiedSince)
}
return http.DefaultTransport.RoundTrip(req)
}
So by doing this I can intercept the call to RoundTrip and add my own header. This now means I can check the resources and see if they return a 304 HTTP status code. For example:
ERRO[0001] Error retrieving repository error="GET https://api.github.com/repos/chef-partners/camsa-setup: 304 []" name=camsa-setup vcs=github
I worked out how to do this after coming across this page - https://github.com/rmichela/go-reddit/blob/bd882abbb7496c54dbde66d92c35ad95d4db1211/authenticator.go#L117

gRPC connectivity problem: How to figure out if it is the server or the client?

I am reading a Golang book named "Go Blueprints". So one of the chapters is about implementing a micro-service. And the communication with that service could be http or gRPC. I think I did everything right, however I can't get gRPC communication work. When I try to ask the server from the client, I get this error:
rpc error: code = Unimplemented desc = unknown service Vault
My question is how to start debugging this? How can I check if the problem is in the server or in the client?
In your implementation, the service name was wrong when you initialised endpoints for Hash and Validate. It should be pb.Vault instead of Vault. So the New method should look like this:
func New(conn *grpc.ClientConn) vault.Service {
var hashEndpoint = grpctransport.NewClient(
conn, "pb.Vault", "Hash",
vault.EncodeGRPCHashRequest,
vault.DecodeGRPCHashResponse,
pb.HashResponse{},
).Endpoint()
var validateEndpoint = grpctransport.NewClient(
conn, "pb.Vault", "Validate",
vault.EncodeGRPCValidateRequest,
vault.DecodeGRPCValidateResponse,
pb.ValidateResponse{},
).Endpoint()
return vault.Endpoints{
HashEndpoint: hashEndpoint,
ValidateEndpoint: validateEndpoint,
}
}
In general, you should consult the generated .pb.go file of the matching proto on how things are named. As you can see, it is not straightforward and it probably depends on the implementation of the proto generators.
In your case, here is what it looks like:
grpc.ServiceDesc{
ServiceName: "pb.Vault",
HandlerType: (*VaultServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Hash",
Handler: _Vault_Hash_Handler,
},
{
MethodName: "Validate",
Handler: _Vault_Validate_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "vault.proto",
}

No response from gorilla/rpc JSON RPC Service

I'm investigating using the gorilla web toolkit to create a simple RPC API. I'm using the example from their documentation and I'm testing using Advanced Rest Client in Chrome and use
http://localhost:1111/api/
and POST the following RAW JSON payload:
{"method":"HelloService.Say","params":[{"Who":"Test"}]}
This reaches the server, I know this as I'm logging it (see code below) and I get a 200 OK response. However I'm getting "Response does not contain any data"
I'm expecting the JSON reply message that is defined in the Say method below. Does anyone have any suggestions as to what the problem is?
package main
import (
"gorilla/mux"
"gorilla/rpc"
"gorilla/rpc/json"
"log"
"net/http"
)
type HelloArgs struct {
Who string
}
type HelloReply struct {
Message string
}
type HelloService struct{}
func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error {
log.Printf(args.Who)
reply.Message = "Hello, " + args.Who + "!"
log.Printf(reply.Message)
return nil
}
func main() {
r := mux.NewRouter()
jsonRPC := rpc.NewServer()
jsonCodec := json.NewCodec()
jsonRPC.RegisterCodec(jsonCodec, "application/json")
jsonRPC.RegisterCodec(jsonCodec, "application/json; charset=UTF-8") // For firefox 11 and other browsers which append the charset=UTF-8
jsonRPC.RegisterService(new(HelloService), "")
r.Handle("/api/", jsonRPC)
http.ListenAndServe(":1111", r)
}
It's because gorilla/rpc/json implements JSON-RPC, which requires three parameters in the request: method, params and id.
Requests without id in JSON-RPC are called notifications and do not have responses.
Check specification for more details.
So, in your case, you need to use following JSON:
{"method":"HelloService.Say","params":[{"Who":"Test"}], "id":"1"}

Resources