echo/v4
// - echo/proxy.go -
package testecho
import (
"net/url"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// This is simple proxy middleware
// Example:
// g := e.Group("/foo")
// g.Use(proxyRequests(barUrl))
func proxyRequests(base string) echo.MiddlewareFunc {
upstream, err := url.Parse(base)
if err != nil {
panic(err)
}
proxy := middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: middleware.NewRandomBalancer([]*middleware.ProxyTarget{
{
Name: "endpoint-service-name",
URL: upstream,
},
}),
})
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
req := c.Request()
req.Host = upstream.Host
c.SetRequest(req)
return proxy(next)(c)
}
}
}
// - echo/helpers.go -
package testecho
import (
"time"
"github.com/golang-jwt/jwt/v4"
)
var secret = []byte("secret")
type TokenClaim struct {
*jwt.StandardClaims
TokenType string
Name string
}
func verifyTokenWithKey(token *jwt.Token) (interface{}, error) {
return secret, nil
}
func createToken(user string) (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
// set our claims
token.Claims = &TokenClaim{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"root",
user,
}
return token.SignedString(secret)
}
// - echo/handler.go -
package testecho
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type handler struct {
users map[string]string
echo http.Handler
}
func Handler(db map[string]string) http.Handler {
e := echo.New()
e.GET("/public", func(ctx echo.Context) error {
return ctx.String(http.StatusOK, "this is public endpoint")
})
e.POST("/authorize", func(ctx echo.Context) error {
user := ctx.FormValue("user")
pass := ctx.FormValue("pass")
for u, p := range db {
if user == u && pass == p {
// create token
token := jwt.New(jwt.SigningMethodHS256)
// set our claims
token.Claims = &TokenClaim{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"root",
u,
}
// generate encoded token and send it as response
t, err := token.SignedString(secret)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return ctx.JSON(http.StatusOK, map[string]string{
"token": t,
})
}
}
return echo.ErrUnauthorized
})
g := e.Group("/restricted", middleware.JWTWithConfig(middleware.JWTConfig{
ParseTokenFunc: func(auth string, c echo.Context) (interface{}, error) {
// claims are of type `jwt.MapClaims` when token is created with `jwt.Parse`
token, err := jwt.Parse(auth, verifyTokenWithKey)
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return token, nil
},
}))
g.POST("", func(ctx echo.Context) error {
user := ctx.Get("user").(*jwt.Token)
if claims, ok := user.Claims.(jwt.MapClaims); ok {
return ctx.String(http.StatusOK, fmt.Sprintf("Welcome, %s\n", claims["Name"]))
}
return ctx.String(http.StatusOK, fmt.Sprintf("Welcome"))
})
return e
}
// - echo/handler_test.go -
package testecho
import (
"httptesting"
"testing"
"github.com/stretchr/testify/suite"
)
func TestEchoHandler(t *testing.T) {
suite.Run(t, &httptesting.HandlerTestSuite{
Handler: Handler(map[string]string{"foo": "bar"}),
})
}
// - echo/error_handler_test.go -
package testecho
import (
"context"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestHTTPErrorHandler(t *testing.T) {
e := echo.New()
defer func() { _ = e.Close() }()
tests := map[string]struct {
err error
status int
message string
}{
"context.DeadlineExceeded": {
context.DeadlineExceeded,
408,
`{"message":"context deadline exceeded"}` + "\n",
},
"context.Canceled": {
context.Canceled,
408,
`{"message":"context canceled"}` + "\n",
},
`echo.HTTPError`: {
&echo.HTTPError{
Code: 418,
Message: "I'm a teapot",
},
418,
`{"message":"I'm a teapot"}` + "\n",
},
`regular_error`: {
errors.New("I'm a teapot"),
http.StatusInternalServerError,
`{"message":"I'm a teapot"}` + "\n",
},
}
for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/", nil)
rec := httptest.NewRecorder()
errorHandler(test.err, e.NewContext(req, rec))
b, _ := ioutil.ReadAll(rec.Body)
assert.Equal(t, test.status, rec.Code, "Error Status Code not match")
assert.Equal(t, test.message, string(b), "Error message not match")
})
}
}
type echoError struct {
Message string `json:"message"`
}
func (e echoError) Error() string {
return e.Message
}
func errorHandler(err error, c echo.Context) {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
c.JSON(http.StatusRequestTimeout, echoError{err.Error()})
return
}
var echoErr *echo.HTTPError
if errors.As(err, &echoErr) {
c.JSON(echoErr.Code, echoErr)
return
}
// Add another Error handling here
c.JSON(http.StatusInternalServerError, echoError{err.Error()})
}