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

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

Related

How to get the ip of the user based on interaction with pod in K8s

I want to get the IP of the user(client) based on his interaction with the Pods, (I'm thinking about getting the user IP and locate him based on his IP)
I made the figure below for a better explanation of my question,
the only thing I was able to find to maybe improve the situation was to patch the service and set externalTrafficPolicy to "Local" so the service will preserve the IP of the user.
But still not sure how or even at which part should I check the IP of the user. is it possible to monitor the activity of the user from outside of pod? any idea?
(I'm using golang)
update: to make it more clear, i'm not creating the pods, in the scenario below clients are responsible and can create different pods and containers even the services they need, it's like a test-bed for them, so i cannot edit their containers file but i may be able to bring up another container beside their conatiner and then maybe use the answer at this post https://stackoverflow.com/a/27971761/9350055 to find the ip of the client. do you think this will work?
Sure, that will work but keep in mind that you will only receive traffic on the nodes where you have pods for the particular service (in your case Service NodePort).
If you are using Golang
Now, this should work with either L4 or L7 traffic. If you are using Golang an example of how to get it is looking at the X-Forwarded-For HTTP header:
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("/", ExampleHandler)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
func ExampleHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
resp, _ := json.Marshal(map[string]string{
"ip": GetIP(r),
})
w.Write(resp)
}
// GetIP gets a requests IP address by reading off the forwarded-for
// header (for proxies) and falls back to use the remote address.
func GetIP(r *http.Request) string {
forwarded := r.Header.Get("X-FORWARDED-FOR")
if forwarded != "" {
return forwarded
}
return r.RemoteAddr
}
Also, here's an example of how to get for L4 services (TCP).
✌️

Code design for handle funcs in go web app

I'm learning go and ran into some design issues while developing web app. The app has main route "/" where user can submit a simple form. With those form values I am calling external API and unmarshaling response into some struct. Now from here I want to make another call based on retrieved values to another external API and I'm not sure what's the proper way of doing this. Here is a snippet for better understandment:
func main() {
http.HandleFunc("/", mainHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
//renders form template
//makes post and retrieves data from api
//here with retrieved data I want to make another call to different API,
// but mainHandler would get too big and complex. I'm not sure how should I pass this data to
// another handler or redirect to another handler with this data.
}
The handlers' semantics should be designed to match the desired HTTP behavior, regardless of the code complexity. If you want to handle a single client request by doing a bunch of stuff, that should be a single handler. If the handler becomes too complex, break it up. Handlers are just functions and can be broken up exactly like any other function - by extracting some part of it into another function and calling that new function. To take you pseudocode:
func mainHandler(w http.ResponseWriter, r *http.Request) {
err := renderTemplate(w)
if err != nil { ... }
err, data := postToApi()
if err != nil { ... }
err, data2 := postToApi2(data)
if err != nil { ... }
}
There's no reason for those functions to be handlers themselves or to get the client involved with a redirect. Just break up your logic the way you normally break up logic - it doesn't matter that it's an HTTP handler.
Hi golearner, in the mainHandler just render the form and make another handler kinda "/formaction" to handle the form, in that way you can easily organize your code.

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.

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

Golang. What to use? http.ServeFile(..) or http.FileServer(..)?

I'm a little bit confused. Much of examples shows usage of both: http.ServeFile(..) and http.FileServer(..), but seems they have very close functionality. Also I have found no information about how to set custom NotFound handler.
// This works and strip "/static/" fragment from path
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// This works too, but "/static2/" fragment remains and need to be striped manually
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
})
http.ListenAndServe(":8080", nil)
I've tried to read source code and both of them use serveFile(ResponseWriter, *Request, FileSystem, string, bool) underlying function. However http.FileServer return fileHandler with its own ServeHTTP() method and make some preparation work before serving file (eg path.Clean()).
So why need this separation? Which method better to use? And how can I set custom NotFound handler, for example when requested file not found?
The main difference is that http.FileServer does effectively almost 1:1 mapping of an HTTP prefix with a filesystem. In plain english, it serves up an entire directory path. and all its children.
Say you had a directory called /home/bob/static and you had this setup:
fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))
Your server would take requests for e.g. /static/foo/bar and serve whatever is at /home/bob/static/foo/bar (or 404)
In contrast, the ServeFile is a lower level helper that can be used to implement something similar to FileServer, or implement your own path munging potentially, and any number of things. It simply takes the named local file and sends it over the HTTP connection. By itself, it won't serve a whole directory prefix (unless you wrote a handler that did some lookup similar to FileServer)
NOTE Serving up a filesystem naively is a potentially dangerous thing (there are potentially ways to break out of the rooted tree) hence I recommend that unless you really know what you're doing, use http.FileServer and http.Dir as they include checks to make sure people can't break out of the FS, which ServeFile doesn't.
Addendum
Your secondary question, how do you do a custom NotFound handler, unfortunately, is not easily answered. Because this is called from internal function serveFile as you noticed, there's no super easy place to break into that. There are potentially some sneaky things like intercepting the response with your own ResponseWriter which intercepts the 404 response code, but I'll leave that exercise to you.
Here a handler which sends a redirect to "/" if file is not found. This comes in handy when adding a fallback for an Angular application, as suggested here, which is served from within a golang service.
Note: This code is not production ready. Only illustrative (at best :-)
package main
import "net/http"
type (
// FallbackResponseWriter wraps an http.Requesthandler and surpresses
// a 404 status code. In such case a given local file will be served.
FallbackResponseWriter struct {
WrappedResponseWriter http.ResponseWriter
FileNotFound bool
}
)
// Header returns the header of the wrapped response writer
func (frw *FallbackResponseWriter) Header() http.Header {
return frw.WrappedResponseWriter.Header()
}
// Write sends bytes to wrapped response writer, in case of FileNotFound
// It surpresses further writes (concealing the fact though)
func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
if frw.FileNotFound {
return len(b), nil
}
return frw.WrappedResponseWriter.Write(b)
}
// WriteHeader sends statusCode to wrapped response writer
func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
if statusCode == http.StatusNotFound {
Log.Printf("INFO: Setting FileNotFound flag\n")
frw.FileNotFound = true
return
}
frw.WrappedResponseWriter.WriteHeader(statusCode)
}
// AddFallbackHandler wraps the handler func in another handler func covering authentication
func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
Log.Printf("INFO: Creating fallback handler")
return func(w http.ResponseWriter, r *http.Request) {
Log.Printf("INFO: Wrapping response writer in fallback response writer")
frw := FallbackResponseWriter{
WrappedResponseWriter: w,
FileNotFound: false,
}
handler(&frw, r)
if frw.FileNotFound {
Log.Printf("INFO: Serving fallback")
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
}
It can be added as in this example (using goji as mux):
mux.Handle(pat.Get("/*"),
AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))

Resources