"extend" http.Request

960 views
Skip to first unread message

stephanos

unread,
Jun 3, 2013, 11:35:57 AM6/3/13
to golan...@googlegroups.com
Hi there,

I want to add "common" methods to the http.Request type, for example an "IsAuthenticated" implementation:

type Request struct {
*http.Request
}

func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":80", nil)
}

func hello(w http.ResponseWriter, r *Request) {
if r.IsAuthenticated() {
fmt.Fprintf(w, "Hello there!")
} else {
fmt.Fprintf(w, "Who are you?")
}
}

func (self *Request) IsAuthenticated() bool {
return false // STUB
}

http://play.golang.org/p/KZagdXjKek

This doesn't work ... how could I achieve this / a similar functionality?

Cheers
Stephan

Rémy Oudompheng

unread,
Jun 3, 2013, 11:40:45 AM6/3/13
to stephanos, golang-nuts
For example, define a type implementing the http.Handler interface.

Rémy.

Keith Rarick

unread,
Jun 3, 2013, 6:05:08 PM6/3/13
to stephanos, golang-nuts
On Mon, Jun 3, 2013 at 8:35 AM, stephanos <stephan...@gmail.com> wrote:
> I want to add "common" methods to the http.Request type, for example an
> "IsAuthenticated" implementation:
> ...
> This doesn't work ... how could I achieve this / a similar functionality?

Write a function:

func IsAuthenticated(r *http.Request) bool

André Moraes

unread,
Jun 3, 2013, 8:04:33 PM6/3/13
to stephanos, golan...@googlegroups.com
>
> http://play.golang.org/p/KZagdXjKek
>
> This doesn't work ... how could I achieve this / a similar functionality?
>

You can't do that because the http.HandlerFunc expects a pointer to
*http.Request and http.Request is a struct. Wrapping http.Request like
you did would work if http.Request was a interface.

Having that said, you could:
* write a IsAuthenticated function (pointed by Keith)
* write a type that implements http.ServeHTTP and method will call
your handler function passing *Request instead of *http.Request

--
André Moraes
http://amoraes.info

stephanos

unread,
Jun 4, 2013, 10:24:46 AM6/4/13
to golan...@googlegroups.com, stephanos
Okay, so far I created my "own" Request type
 
package web

type Request struct {
*http.Request
}

as well as my own HandlerFunc

type appHandlerFunc func(Response, *Request)

func (f appHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f(w, &Request{r})
}

So far it works and I can serve HTTP requests. BUT I was just about to integrate a 3rd-party library to handle Basic Authentication.
Its function receives a http.Request, so naturally my object oriented mind ordered me to throw in my web.Request object, and then ...

cannot use req (type *web.Request) as type *http.Request in function argument

Is this fundamentally wrong, or do I "just" have to do some casting?

Christoph Hack

unread,
Jun 4, 2013, 1:13:53 PM6/4/13
to golan...@googlegroups.com, stephanos
There are several ways to archive your goal.

Solution 1:

Implement a "func IsAuthenticated(r *http.Request) bool" method and use it like this in your handlers:

func handleFoo(w http.ResponseWriter, r *http.Request) {
    if !IsAuthenticated(r) {
      http.Redirect(w, r, "/login", http.StatusSeeOther)
      return
    }
    // process the request
}


Solution 2:

Implement a "AuthOnly" middleware. Something like this:

type AuthHandler struct {
  h http.Handler
}

func (a AuthHandler) ServeHttp(w http.ResponseWriter, r *http.Request) {
    if !IsAuthenticated(r) {
      http.Redirect(w, r, "/login", http.StatusSeeOther)
      return
    }
    h.ServeHttp(w, r)
}

func AuthOnly(h http.Handler) http.Handler {
    return AuthHandler{h}
}

Usage example:

http.Handle("/login", loginHandler)
http.Handle("/admin", AuthOnly(adminHandler))
// etc.

Solution 3:

Use your own handler signature. For example:

type Context struct {
  req http.Request
  currentUser string
  // and a lot more App specific things
}

type HandleFunc func (w http.ResponseWriter, ctx *Context) error

func (h HandleFunc) ServeHttp(w http.ResponseWriter, r *http.Request) {
  ctx := &Context{req: r, currentUser: "anonymous"}
  err := h(w, ctx)
  if err != nil {
    http.Error(w, err.String(), http.StatusInternalServerError)
    return
  }
}

And now, you can implement your own handlers:

func handleAdmin(w http.ResponseWriter, ctx *Context) error {
  // todo
}

http.Handle("/admin", HandleFunc(handleAdmin))


Solution 4:

Stay with the http.Handler interface, but generate a Context object if you need one. This approach is used by the AppEngine for Go library.

Example:

type Context struct {
  req *http.Request
  currentUser string
  // and a lot more App specific things
}

func NewContext(r *http.Request) *Context {
  return &Context{req: r}
}

type (ctx *Context) IsAuthenticated() bool {
  // todo
}

func handleAdmin(w http.ResponseWriter, r *http.Request) {
  ctx := NewContext(r)
  if !ctx.IsAuthenticated() {
    // ...
  }
}



I haven't tested any code in this post, so the samples might include a lot of minor mistakes, but I hope you got the general approach. According to your first post, solution 1 should be sufficient, but solution 2 might be a nice addition. Solution 3 and 4 is only useful in bigger projects I think. Solution 3 has the advantage that you can also change the handler interface so that you can return error's directly, but those Handlers need your custom middleware to be compatible with the http package. Which solution is suited best for you depends heavily on your project. So pick the appropriate one yourself.

-christoph

André Moraes

unread,
Jun 4, 2013, 4:19:13 PM6/4/13
to stephanos, golan...@googlegroups.com
>
> package web
>
>
> type Request struct {
> *http.Request
> }
>
>
> as well as my own HandlerFunc
>
> type appHandlerFunc func(Response, *Request)
>
> func (f appHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
> f(w, &Request{r})
> }
>

func checkAuth(w Response, req *Request) {
// req here is web.Request
theModuleIWantToUse.IsAuthenticated(w.ResponseWriter, req.Request)
}

If the theModuleIWantToUse also uses a wrapper to http.Request, your
request type will need to wrap theModuleIWantToUse.Request instead of
http.Request.

Also take a look at:

http://www.gorillatoolkit.org/pkg/sessions
http://www.gorillatoolkit.org/pkg/context
Message has been deleted

stephanos

unread,
Jun 5, 2013, 3:40:32 AM6/5/13
to golan...@googlegroups.com, stephanos
Thank you Christoph and André! I love how flexible Go is despite being so simple.
Furthermore the Go community is extremely helpful. Kudos :)
Reply all
Reply to author
Forward
0 new messages