golang gin-gonic/gin framework use another param instead of callback - go

in my case, I want to use anothor param instead of callback
my url: http://example.com?id=1&cb=callback1
but I found this in the source code:
// JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}
how can I use the param cb instead of callback

You can use middleware for gin. Modifying the query before it is parsed.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/url"
)
func main() {
r := gin.Default()
r.Use(func(context *gin.Context) {
req := context.Request
urlCopy, _ := req.URL.Parse(req.RequestURI)
if cb := urlCopy.Query().Get("cb"); cb != "" {
req.URL.RawQuery += fmt.Sprintf("&callback=%s", url.QueryEscape(cb))
}
})
r.GET("/ping", func(c *gin.Context) {
c.JSONP(400, 1)
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

Related

How to apply Chi middleware for subroutes?

Given the following sample API using Chi
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
http.ListenAndServe(":3000", GetRouter())
}
func GetRouter() *chi.Mux {
apiRouter := chi.NewRouter()
apiRouter.Route("/foo-group", func(fooGroupRouter chi.Router) {
fooGroupRouter.Use(middleware.AllowContentType("application/json"))
fooGroupRouter.Post("/sub-route", HandleRoute( /* Params */))
})
// other routes
return apiRouter
}
func HandleRoute( /* Params */) http.HandlerFunc {
return func(responseWriter http.ResponseWriter, request *http.Request) {
responseWriter.WriteHeader(http.StatusCreated)
responseWriter.Write([]byte("done"))
}
}
When calling the API via
POST localhost:3000/foo-group/sub-route
I get a 201 with "done". But I want to ensure this endpoint only accepts the content type "application/json", otherwise send back a 415.
Unfortunately the middleware is not working yet. I also tried to test the behaviour with the testrunner
package main
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
)
func TestHandleRoute(suite *testing.T) {
server := httptest.NewServer(HandleRoute())
suite.Run("responds with status code "+strconv.Itoa(http.StatusUnsupportedMediaType)+" if content type is not application/json", func(testing *testing.T) {
response, _ := http.Post(server.URL, "text/xml", nil)
if response.StatusCode != http.StatusUnsupportedMediaType {
testing.Errorf("Expected statuscode %d but got %d", http.StatusUnsupportedMediaType, response.StatusCode)
}
})
}
Unfortunately the test fails with the message
main_test.go:17: Expected statuscode 415 but got 201
so it seems the middleware didn't run. How can I fix that?

function variable in struct resets to nil

I'm trying to make HTTP proxy using go net/http. I made struct type named HTTPProxy to store http.Server object and some hooks to modify Request and Response.
There're two hooks in my proxy(Request/Response hook). The hooks can be set with interface named SetRequestHook and SetResultHook. I checked value of HTTPProxy struct using fmt.Printf("%#v\n", httpProxy) before/after SetRequestHook to check the set of request hook was successful. This is the value of httpProxy before/after SetRequestHook.
webproxy.HTTPProxy{server:(*http.Server)(0xc000154000), IsStarted:false, requestHook:(webproxy.RequestHookFunc)(nil), responseHook:(webproxy.ResponseHookFunc)(nil)}
webproxy.HTTPProxy{server:(*http.Server)(0xc000154000), IsStarted:false, requestHook:(webproxy.RequestHookFunc)(0x75ce60), responseHook:(webproxy.ResponseHookFunc)(nil)}
As you can see address of request hook function stored well in httpProxy struct. But if I call another interface names Start requestHook in struct resets to nil for no reason. This is the result of fmt.Printf("%#v\n", httpProxy) in Start interface.
webproxy.HTTPProxy{server:(*http.Server)(0xc000154000), IsStarted:false, requestHook:(webproxy.RequestHookFunc)(nil), responseHook:(webproxy.ResponseHookFunc)(nil)}
I haven't reset my struct since initialize so I'm not even sure what is happening to my struct. How can I make my request hook to not reset to nil? This is my current code having this problem.
cmd/runserver/main.go
package main
import (
"log"
"net/http"
"test/pkg/webproxy"
)
var a int
func test(request *http.Request) {
log.Printf("New Request: %d\n", a)
a++
}
func main() {
a = 1
httpProxy := webproxy.NewHTTPProxy()
httpProxy.SetRequestHook(test)
httpProxy.Start("", 3000)
}
pkg/webproxy/webproxy.go
// Package webproxy provides http and https proxy with request and response hooks
package webproxy
import (
"context"
"fmt"
"log"
"net"
"net/http"
)
// RequestHookFunc typed functions provided to proxy will modify request before sent to the server.
type RequestHookFunc func(request *http.Request)
// ResponseHookFunc typed functions provided to proxy will modify response before sent to the client.
type ResponseHookFunc func(response *http.Response)
// HTTPProxy will store some items that are used in HTTP proxy and state.
type HTTPProxy struct {
server *http.Server
IsStarted bool
requestHook RequestHookFunc
responseHook ResponseHookFunc
}
// ProxyManager provide methods to manage HTTPProxy and HTTPSProxy.
type ProxyManager interface {
Start(host string, port uint16)
Stop()
GetAddr()
SetRequestHook(hook RequestHookFunc)
SetResponseHook(hook ResponseHookFunc)
}
// NewHTTPProxy creates default HTTPProxy struct.
func NewHTTPProxy() *HTTPProxy {
return &HTTPProxy{
server: new(http.Server),
IsStarted: false,
requestHook: nil,
responseHook: nil,
}
}
// Start serves HTTPProxy on host:port.
func (httpProxy HTTPProxy) Start(host string, port uint16) {
if httpProxy.IsStarted {
log.Println("[HTTP] The proxy is already running.")
return
} else if len(host) != 0 && net.ParseIP(host) == nil {
log.Fatal("[HTTP] Inserted host is not valid.")
}
addr := fmt.Sprintf("%s:%d", host, port)
fmt.Printf("%#v\n", httpProxy) // requestHook = nil. Why?? I called SetRequestHook before run Start
httpProxy.server.Handler = getHandler(httpProxy.requestHook, httpProxy.responseHook)
httpProxy.server.Addr = addr
log.Printf("[HTTP] The proxy will be served on %s:%d\n", host, port)
if err := httpProxy.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("[HTTP] The proxy ListenAndServer: %v\n", err)
}
}
// Stop will stop HTTPProxy immediately.
func (httpProxy HTTPProxy) Stop() {
if !httpProxy.IsStarted {
log.Println("[HTTP] The proxy is not running.")
return
}
httpProxy.server.Shutdown(context.Background())
}
// GetAddr will return HTTPProxy's host:port.
func (httpProxy HTTPProxy) GetAddr() string {
return httpProxy.server.Addr
}
// SetRequestHook setup request hook into the proxy
func (httpProxy HTTPProxy) SetRequestHook(hook RequestHookFunc) {
if httpProxy.IsStarted {
log.Println("[HTTP] You can't set request hook while the proxy is working.")
return
}
fmt.Printf("%#v\n", httpProxy) // requestHook = nil
httpProxy.requestHook = hook
fmt.Printf("%#v\n", httpProxy) // requestHook = [hook's address]
}
// SetResponseHook setup response hook into the proxy.
func (httpProxy HTTPProxy) SetResponseHook(hook ResponseHookFunc) {
if httpProxy.IsStarted {
log.Println("[HTTP] You can't set response hook while the proxy is working.")
return
}
httpProxy.responseHook = hook
}
pkg/webproxy/proxyhandler.go
package webproxy
import (
"fmt"
"io"
"net/http"
)
type proxyHandler struct {
requestHook RequestHookFunc
responseHook ResponseHookFunc
}
func getHandler(requestHookIn RequestHookFunc, responseHookIn ResponseHookFunc) http.Handler {
fmt.Printf("%#v\n", requestHookIn)
return &proxyHandler{requestHook: requestHookIn, responseHook: responseHookIn}
}
// ServeHttp
func (handler *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%#v\n", handler.requestHook)
if handler.requestHook != nil {
fmt.Println("Yaha")
handler.requestHook(r)
}
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
io.WriteString(w, "This HTTP response has both headers before this text and trailers at the end.\n")
}

How to get matched route in context in Gin?

I have this code :
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.GET("/user/:id", func(c *gin.Context) {
// How can I get the litteral string "/user/:id" here ?
c.JSON(http.StatusOK, gin.H{"message": "received request"})
})
}
Is there any way that I can retrieve inside the handler the litteral string /user/:id? If I use c.Request.Path it will give me the full output of the path like /user/10.
According to the documentation you can use FullPath().
router.GET("/user/:id", func(c *gin.Context) {
c.FullPath() == "/user/:id" // true
})

Mapping all routes and its http methods in Goji

I would like to map each route and it's request type (GET, POST, PUT, ...) to generate something like a sitemap.xml in JSON for my restful API.
Goji uses functions to create a new route. I could store the paths and handlers in a map.
My approach would be something like this, except that the compiler gives the following initialization loop error, because sitemap and routes refer to each other (the routemap contains the handler sitemap that should marhsall itself).
main.go:18: initialization loop:
main.go:18 routes refers to
main.go:41 sitemap refers to
main.go:18 routes
Can this be achieved in a more idiomatic way?
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
func NewRoute(method, pattern string, handler web.HandlerType) {
switch method {
case "Get", "get":
goji.DefaultMux.Get(pattern, handler)
case "Post", "post":
goji.DefaultMux.Post(pattern, handler)
// and so on...
}
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
// BUG: sitemap tries to marshall itself recursively
resp, _ := json.MarshalIndent(routes, "", " ")
// some error handling...
w.Write(resp)
}
func main() {
for _, r := range routes {
NewRoute(r.Method, r.Pattern, r.Handler)
}
goji.Serve()
}
The easiest way to avoid the initialization loop is to break the loop by delaying one of the initializations.
E.g.:
var routes []Route
func init() {
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
}
}
With this change your code compiles.
[Edit after chat:]
A fully edited and runnable example that also addresses your question about the switch follows:
package main
import (
"encoding/json"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
var routes []Route
func init() {
// Initialzed in init() to avoid an initialization loop
// since `routes` refers to `sitemap` refers to `routes`.
routes = []Route{
Route{"Get", "/index", hello},
Route{"Get", "/sitemap", sitemap},
//Route{"Post", "/somewhereElse", postHandlerExample},
}
}
type Route struct {
Method string `json:"method"`
Pattern string `json:"pattern"`
Handler web.HandlerType `json:"-"`
}
var methods = map[string]func(web.PatternType, web.HandlerType){
"Get": goji.Get,
"Post": goji.Post,
// … others?
}
func (r Route) Add() {
//log.Println("adding", r)
methods[r.Method](r.Pattern, r.Handler)
}
func hello(c web.C, w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
func sitemap(c web.C, w http.ResponseWriter, r *http.Request) {
resp, err := json.MarshalIndent(routes, "", " ")
if err != nil {
http.Error(w, "Can't generate response properly.", 500)
return
}
w.Write(resp)
}
func main() {
for _, r := range routes {
r.Add()
}
goji.Serve()
}
Available as a gist.
I'll note there is nothing wrong with a switch like you had it,
and in this case if there are only two methods a map may be overkill.
A previous version of the example
didn't use a map and explicitly specified both the function and method name (which were expected to match).
Also this version doesn't check for invalid method names (which if routes is always hard coded and never changed at runtime is reasonable). It would be straight forward to do fn, ok := methods[r.Method] and do something else if/when !ok if desired.

How to test functions that implement gorilla context

I wrote a functions that save data into redis database server. The challenge is that I want to test these functions and do not know how to test it.
I just start somehow with
Functions
package sessrage
/*
* Save data into redis database. In the common case,
* the data will be only valid during a request. Use
* hash datatype in redis.
*/
import (
"../context"
"github.com/garyburd/redigo/redis"
"net/http"
)
const (
protocol string = "tcp"
port string = ":6379"
)
func connectAndCloseRedis(connectCall func(con redis.Conn)) {
c, err := redis.Dial("tcp", ":6379")
defer c.Close()
if err != nil {
panic(err.Error())
}
connectCall(c)
}
func PostSessionData(r *http.Request, key, value string) {
go connectAndCloseRedis(func(con redis.Conn) {
sessionId := context.Get(r, context.JwtId).(string)
con.Do("HMSET", sessionId, key, value)
})
}
func GetSessionData(r *http.Request, key string) interface{} {
var result interface{}
sessionId := context.Get(r, context.JwtId).(string)
reply, _ := redis.Values(c.Do("HMGET", sessionId, key))
redis.Scan(reply, &result)
return result
}
and the test file
package sessrage
import (
//"fmt"
"../context"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var server *httptest.Server
var glrw http.ResponseWriter
var glr *http.Request
func init() {
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
glrw = rw
glr = r
context.Set(glr, context.JwtId, "TestId")
}))
}
func TestPostAndGetSession(t *testing.T) {
Convey("POST and GET data on redis.", t, func() {
PostSessionData(glr, "key1", "value1")
time.Sleep(time.Second * 10)
v := GetSessionData(glr, "key1")
assert.Equal(t, "value1", v)
})
}
when I try to run the test I've got
an't load package: ......./sessrage.go:10:2: local import "../context" in non-local package
and the context package looks like
package context
import (
"github.com/gorilla/context"
"net/http"
)
type contextKey int
const (
LanguageId contextKey = iota
JwtId
)
func Get(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func Set(r *http.Request, key, val interface{}) {
context.Set(r, key, val)
}
What do I wrong?
That is the first time, I am testing code in conjunction with http. It seems to be very hard to test.
There are a few issues:
Don't use relative import paths.
Use a pool instead of dialing redis on every action.
The call to sessionId := context.Get(r, context.JwtId).(string) in the PostSessionData anonymous function can fail if the mux or something higher in the call chain clears the Gorilla context before the goroutine runs. Do this instead:
func PostSessionData(r *http.Request, key, value string) {
c := pool.Get()
defer c.Close()
sessionId := context.Get(r, context.JwtId).(string)
if err := c.Do("HMSET", sessionId, key, value); err != nil {
// handle error
}
}

Resources