Amazon API Gateway HTTP API: Custom types in lambda functions in go - go

I am a little bit confused about how to get custom types passed into my Lambda function using golang and sitting behind a HttpApi.
Consider the following go lambda handler, which is almost a copy of the example from the documentation.
type MyRequestType struct {
Name string `json:"name"`
Age int `json:"age"`
}
type MyResponseType struct {
Message string `json:"message"`
}
func handler(request MyRequestType) (MyResponseType, error) {
log.Printf("received request: %v", request)
return MyResponseType{Message: fmt.Sprintf("Hello %s, you are %d years old!", request.Name, request.Age)}, nil
}
func main() {
lambda.Start(handler)
}
The resulting message is always the following.
{
"message": "Hello , you are 0 years old!"
}
I have the dump feeling that this is not possible in Amazon API Gateway HTTP API.
But I also haven't found any documentation pointing out, that this is not possible. So I really wonder, if I am doing something wrong?
The documentation says also something about the valid signatures:
For example func (context.Context, TIn) (TOut, error)
If I am using a HTTP API with the Payload format version 2:
Is the context.Context the normal golang context or something special?
I am thinking of events.APIGatewayV2HTTPRequestContext or others.
What would be the right type of TIn and TOut => events.APIGatewayV2HTTPRequest and events.APIGatewayV2HTTPResponse?

Is the context.Context the normal golang context
Yes.
But you can get the Lambda context with lambdacontext.FromContext which contains additional lambda-specific metadata.
What would be the right type of TIn and TOut
It depends on who invokes the Lambda. When the Lambda is invoked by another AWS service, including the API Gateway, the so-called TIn and TOut are types from the lambda event package. Quote from the package introduction:
This package provides input types for Lambda functions that process AWS events.
In case of the API Gateway, that would be events.APIGatewayProxyRequest and Response, or presumably for the Payload format version 2 the events.APIGatewayV2HTTPRequest and Response — which were added in v1.16.0.
Further documentation (but not much) in the github repo README

Related

Transforming echo.Context to context.Context

I am writing a web application, using Go as the backend. I'm using this GraphQL library (link), and the Echo web framework (link). The problem is that that the graphql-go library uses the context package in Go, while Echo uses its own custom context, and removed support for the standard context package.
My question would be, is there a way to use context.Context as echo.Context, in the following example?
api.go
func (api *API) Bind(group *echo.Group) {
group.Use(middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte("SOME_REAL_SECRET_KEY"),
SigningMethod: "HS256",
}))
group.GET("/graphql", api.GraphQLHandler)
group.POST("/query", echo.WrapHandler(&relay.Handler{Schema: schema}))
}
graphql.go
func (r *Resolver) Viewer(ctx context.Context, arg *struct{ Token *string }) (*viewerResolver, error) {
token := ctx.Value("user").(*jwt.Token) // oops on this line, graphql-go uses context.Context, but the echo middleware provides echo.Context
...
}
How would I make the echo context available to my graphql resolvers. Thank you very much, any help is appreciated.
An echo.Context can't be used as a context.Context, but it does refer to one; if c is the echo context then c.Request().Context() is the context.Context for the incoming request, which will be canceled if the user closes the connection, for instance.
You can copy other useful values from the echo context to the stdlib context, if needed, by using the Get method on the one hand and the WithValue method on the other. If there are some values that you always want copied, then you can write a helper function to do so.

Cannot use implementation of interface as argument to func that wants interface

I'm getting the following error:
./main.go:31: cannot use telegramService (type messaging.TelegramService) as type mypackage.MessagingService in argument to mypackage.RegisterMessagingService:
messaging.TelegramService does not implement mypackage.MessagingService (wrong type for HandleIncomingMessage method)
have HandleIncomingMessage(telegram.Message) error
want HandleIncomingMessage(mypackage.IncomingMessage) error
I have an interface that describes a messaging service like Telegram or WhatsApp, and an interface that describes an incoming message from one of those services:
// IncomingMessage is a message that comes in on a messaging service
type IncomingMessage interface {
Send() error
}
// MessagingService is a service on which messages can be send (like Telegram or FB Messenger)
type MessagingService interface {
Start()
HandleIncomingMessage(IncomingMessage) error
GetHTTPHandler() http.HandlerFunc
GetCommands() []MessagingCommand
}
The first implementation of MessagingService is for Telegram. The issue is the HandleIncomingMessage function, which currently doesn't really do anything and just looks like this:
// HandleIncomingMessage will take an incoming message and repond to it
func (s TelegramService) HandleIncomingMessage(msg *telegram.Message) error {
return nil
}
The issue is that this function accepts a telegram.Message, which the compiler says doesn't comply with the interface. The thing is, that telegram.Message is an implementation of IncomingMessage:
// Message is a Telegram message
type Message struct {
// Added the line below at some point, but it didn't work without it either
mypackage.IncomingMessage
MessageID uint64 `json:"message_id"`
FirstName string `json:"first_name"`
Username string `json:"username"`
Date uint64 `json:"date"`
Text string `json:"text"`
Chat Chat `json:"chat"`
From User `json:"from"`
}
// Send will take m and send it
func (m Message) Send() error {
// Do stuff
return nil
}
Initially IncomingMessage was an empty interface, which is where I first noticed the issue. I tried adding the function Send() which I was going to add anyway, as I thought maybe just giving it any struct wouldnt't work. However, I'm still getting this error.
I don't see any reason why telegram.Message doesn't implement the interface, it's pretty straight forward.
Can anyone explain why this doesn't work?
PS: My package isn't actually called mypackage, changed for clarity
HandleIncomingMessage must take an IncomingMessage argument since that's the way the interface is defined. You can't define an implementation of HandleIncomingMessage that takes some other type as the argument, even if that type implements IncomingMessage. You can define your function to take IncomingMessage and convert that to *telegram.Message using a type assertion:
func (s TelegramService) HandleIncomingMessage(im IncomingMessage) error {
msg := im.(*telegram.Message)
return nil
}
I'm assuming you actually want to be using a pointer to telegram.Message. If so, you need to change the definition of the Send method to take a pointer receiver.

Is there a way use mux routing to select a handler without using http listenAndServe for golang?

There are a lot of mux routers for golang. All of the ones I've found assume that I'm building my own HTTP server in go. However, I would like to use aws apigateway as the external layer and have it forward the method, path, query parameters to a lambda function that I have deployed with apex (go shim for aws lambda functions). All the API gateway endpoints will forward to one lambda function so that there are fewer things to hook up, like permissions and so forth.
So I would like to use nice mux libraries for their ability to parse regex or path variables, but use them inside the Lambda and be able to invoke the correct handler based on the url path.
Most of the mux routers have a usage like this:
router := NewRouter()
router.Add("GET", "/my_path/:id", MyHandler)
Where MyHandler is a type of http.HandlerFunc
Then the server is started with something like http.ListenAndServe(port, router)
But in AWS Lambda there is no server to start, I would just like to use the mux to find the handler that I should be calling.
I have created a lib for this purpose.
The basic idea is to transform apigateway request context which is a json object into http.Request
The most common approach is to use apex/gateway which is a drop in replacement for net/http on AWS Lambda. This is started by calling with the same parameters.
gateway.ListenAndServe(port, router)
Here is a sample showing it's use:
func main() {
http.HandleFunc("/", hello)
log.Fatal(gateway.ListenAndServe(":3000", nil))
}
func hello(w http.ResponseWriter, r *http.Request) {
// example retrieving values from the api gateway proxy request context.
requestContext, ok := gateway.RequestContext(r.Context())
if !ok || requestContext.Authorizer["sub"] == nil {
fmt.Fprint(w, "Hello World from Go")
return
}
userID := requestContext.Authorizer["sub"].(string)
fmt.Fprintf(w, "Hello %s from Go", userID)
}
It's also common to use this with the chi router.
Some other options include:
davyzhang/agw (listed in Davy's response)
davidsbond/lux
EtienneBruines/fasthttplambda

Change *http.Client transport

The Status Quo
Having picked a side project (building a wrapper around a third party API), I'm stuck. I am using sling to compose my HTTP requests.
So parts of the Client are composed as follows:
type Client struct {
// some services etc..
sling *sling.Sling <-- this is initialized with *http.Client
}
func NewClient(httpClient *http.Client) *Client {
sling := sling.New().Client(httpClient).Base(BaseURL)
}
//....
Things I can't wrap my head around
I am following the same principle as go-github and go-twitter that authentication should not be handled by my library, but rather by golangs oauth1/2 package.
As the the API provides application and user level authentication and some of the workflows require initial application level authentication and then user level authentication, my question is, if there is any way to change the *http.Transport in order to change the authentication header on a client basis.
So far, I haven't found a way to do so.
The http.Client has a Transport field that you could use to "change the authentication header on a client basis" if that's what you want. The Transport field has type http.RoundTripper which is a one method interface, so all you need to do is to define your transport with an implementation of the RoundTrip method.
type MyTransport struct {
apiKey string
// keep a reference to the client's original transport
rt http.RoundTripper
}
func (t *MyTransport) RoundTrip(r *http.Request) (*http.Response, error) {
// set your auth headers here
r.Header.Set("Auth", t.apiKey)
return t.rt.RoundTrip(r)
}
Now you can use an instance of this type to set the Transport field on an http.Client.
var client *http.Client = // get client from somewhere...
// set the transport to your type
client.Transport = &MyTransport{apiKey: "secret", tr: client.Transport}
Depending on how and where from you got the client, it's possible that its Transport field is not yet set, so it might be a good idea to ensure that your type uses the default transport in such a case.
func (t *MyTransport) transport() http.RoundTripper {
if t.rt != nil {
return t.rt
}
return http.DefaultTransport
}
// update your method accordingly
func (t *MyTransport) RoundTrip(r *http.Request) (*http.Response, error) {
// set your auth headers here
r.Header.Set("Auth", t.apiKey)
return t.transport().RoundTrip(r)
}
It might be worth noting that the Go documentation recommends not to modify the *http.Request inside the RoundTrip method, so what you can do, and what the go-github package you linked to is doing, is to create a copy of the request, set the auth headers on it, and pass that to the underlying Transport. See here: https://github.com/google/go-github/blob/d23570d44313ca73dbcaadec71fc43eca4d29f8b/github/github.go#L841-L875

Golang service/daos implementation

Coming from a Java background, I have some questions on how things are typically done in Golang. I am specifically talking about services and dao's/repositories.
In java, I would use dependency injection (probably as singleton/application-scoped), and have a Service injected into my rest endpoint / resource.
To give a bit more context. Imagine the following Golang code:
func main() {
http.ListenAndServe("localhost:8080", nil)
}
func init() {
r := httptreemux.New()
api := r.NewGroup("/api/v1")
api.GET("/blogs", GetAllBlogs)
http.Handle("/", r)
}
Copied this directly from my code, main and init are split because google app engine.
So for now I have one handler. In that handler, I expect to interact with a BlogService.
The question is, where, and in what scope should I instantiate a BlogService struct and a dao like datastructure?
Should I do it everytime the handler is triggered, or make it constant/global?
For completeness, here is the handler and blogService:
// GetAllBlogs Retrieves all blogs from GCloud datastore
func GetAllBlogs(w http.ResponseWriter, req *http.Request, params map[string]string) {
c := appengine.NewContext(req)
// need a reference to Blog Service at this point, where to instantiate?
}
type blogService struct{}
// Blog contains the content and meta data for a blog post.
type Blog struct {...}
// newBlogService constructs a new service to operate on Blogs.
func newBlogService() *blogService {
return &blogService{}
}
func (s *blogService) ListBlogs(ctx context.Context) ([]*Blog, error) {
// Do some dao-ey / repository things, where to instantiate BlogDao?
}
You can use context.Context to pass request scoped values into your handlers (available in Go 1.7) , if you build all your required dependencies during the request/response cycle (which you should to avoid race conditions, except for dependencies that manage concurrency on their own like sql.DB). Put all your services into a single container for instance, then query the context for that value :
container := request.Context.Value("container").(*Container)
blogs,err := container.GetBlogService().ListBlogs()
read the following material :
https://golang.org/pkg/context/
https://golang.org/pkg/net/http/#Request.Context

Resources