URL Variables

9,139 views
Skip to first unread message

Michael Beale

unread,
Jun 12, 2011, 6:40:22 PM6/12/11
to golang-nuts
I'm new to Go and have been looking around on these forums for a while
trying to find an answer but no luck. Here it is, is it possible
using the http package to extract variables out of the URL or can it
only get information separated by '/'? For instance given the handler
of "/v1/items/{id}.{formatvar}", is it possible to get these variables
within the handler function? If not, any ideas on how I could
implement this? I'm trying to change my mode of thinking from php to
Go with a small project so I would appreciate any advice!!

Kyle Lemons

unread,
Jun 12, 2011, 6:54:12 PM6/12/11
to Michael Beale, golang-nuts
It does not directly support that, but you can set a handler for
/v1/id/ and use the Path in the *http.Request (along with
strings.Split) to get what you need.

----
Kyle Lemons
Georgia Tech Computer Engineering '10

Sent from my iPhone, so pardon any spelling or punctuation issues!

On Jun 12, 2011, at 3:40 PM, Michael Beale

Evan Shaw

unread,
Jun 12, 2011, 7:03:46 PM6/12/11
to Michael Beale, golang-nuts

The easiest way without using third-party packages would be to use
regular expressions and capture the portions you want. For your
example, you could do something like:

r := regexp.MustCompile(`/v1/items/([0-9]*)\.(.*)`)
vars := r.FindStringSubmatch(req.URL.Path)

(Assuming that id is a number and formatvar can be anything.)

The downside is that you can't name your submatches, so you have to
know the order. You also probably don't want to compile the regular
expression every single time you run the handler. :)

- Evan

Christoffer Hallas

unread,
Jun 12, 2011, 10:58:30 PM6/12/11
to golang-nuts
I understand your problem like so:

You want to access the different parts of the Path in a http.URL
structure?

There are atleast two ways to go on about this; you can use
strings.Split so that you get a slice of the parts, seperated ofc by a
forward slash, or you can use fmt.Scan (or some variant) to create
format patterns that matches the paths of you're desire. I suggest you
simply check out those packages I listed.

Hope this helps.

Kind regards

Christoffer Hallas

On Jun 13, 12:40 am, Michael Beale

Ibrahim M. Ghazal

unread,
Jun 12, 2011, 11:50:36 PM6/12/11
to Michael Beale, golan...@googlegroups.com
On Jun 13, 1:40 am, Michael Beale <michael.be...@restaurantsentry.com>
wrote:

If you don't mind using http query urls (e.g.: /v1/items/?id={id}
&formatvar={formatvar} ) you can use http.ParseQuery or
http.Request.FormValue.

Michael Beale

unread,
Jun 27, 2011, 11:37:08 PM6/27/11
to golang-nuts
Thanks for all the comments. I went ahead and wrote a router class
located at https://github.com/rsentry/gorouter
It hasn't been battle tested and would consider it to be very alpha.

This is my first attempt at a go program so any comments on my code
would be appreciated. I think there is a more elegant way
of handling the handlers. Maybe using interfaces more? Also with the
mapping of url variables , the best way I thought of was using
a map that contains key/values for the url variables, anybody have any
different ideas?

Another question I have that was confusing me a bit was the slices
that I created to store routes. I tried to add routes to the slices
without
initializing them but got an "out of bounds" error. My code is this:

n := len(router.post)
router.post = router.post[0:n+1]
router.post[n] = route

The way I got around it was initializing each var like:

router.post = make([]Route,0,100)

I read the go spec but don't seem to understand why I need to
initialize it. Maybe because it is just a pointer and not an actual
array?

Anyways, all comments/critiques are appreciated

Nigel Tao

unread,
Jun 27, 2011, 11:53:19 PM6/27/11
to Michael Beale, golang-nuts
On 28 June 2011 13:37, Michael Beale <michae...@restaurantsentry.com> wrote:
> n := len(router.post)
> router.post = router.post[0:n+1]
> router.post[n] = route

router.post = append(router.post, route)

Evan Shaw

unread,
Jun 28, 2011, 12:07:16 AM6/28/11
to Michael Beale, golang-nuts
On Tue, Jun 28, 2011 at 3:37 PM, Michael Beale
<michae...@restaurantsentry.com> wrote:
> This is my first attempt at a go program so any comments on my code
> would be appreciated.  I think there is a more elegant way
> of handling the handlers.  Maybe using interfaces more?

You could do something similar to what the http package does and
create a Handler interface:

type Handler interface {
Serve(w http.ResponseWriter, r *http.Request, v map[string]string)
}

if you want to continue passing lone functions as handlers, you can
follow the http package again and create a HandlerFunc type.

> Also with the
> mapping of url variables , the best way I thought of was using
> a map that contains key/values for the url variables, anybody have any
> different ideas?

That's probably the best way without getting into reflection. Even
with reflection your options are somewhat limited since function
parameter names aren't exposed.

> Another question I have that was confusing me a bit was the slices
> that I created to store routes.  I tried to add routes to the slices
> without
> initializing them but got an "out of bounds" error.  My code is this:

That's because the zero value for a slice has a length and capacity of
zero. Thus any index into it is out of bounds and when you want to
expand, there's nowhere to go.

> n := len(router.post)
> router.post = router.post[0:n+1]
> router.post[n] = route

A better way to do it would be to use append. Replace those three lines with:

router.post = append(router.post, route)

If you do this, you don't necessarily have to preallocate the slices,
but you can choose to for efficiency reasons.

You're going to be unnecessarily redoing a lot of work for every
request in findRouterMatch. You should move some of that work into
AddRoute so it's done only once.

Also, I'd find your code easier to read if you ran gofmt on it. ;)

- Evan

Islan Dberry

unread,
Jun 28, 2011, 12:29:39 AM6/28/11
to golan...@googlegroups.com
 the best way I thought of was using a map that contains
> key/values for the url variables, anybody have any 
> different ideas? 

The web.go router passes the values as arguments to the handler function. 

The twister router adds the url variables to the request parameters. This is not ideal because the url variable can clobber a request parameter.


Islan Dberry

unread,
Jun 28, 2011, 1:07:21 AM6/28/11
to golan...@googlegroups.com
> You could do something similar to what the http package
> does and create a Handler interface:
>
> type Handler interface {
>    Serve(w http.ResponseWriter, r *http.Request, v map[string]string)
> }

The problem with this approach is that the handlers don't compose with other handlers written to the http Handler interface. It would be nice if http.Request had a map[string]interface{} field where gorouter and other handlers can store arbitrary data associated with the request. 

If the following field is added to http.Request

    Env map[string]interface{}

then gorouter can provide the following function for handlers to get url variables:

func urlVariable(r *http.Request, name string) string {
    if urlVariables, ok := r.Env["gorouter"].(map[string]string); ok {
       return urlVariables[name]
   }
   return ""
}

The Env map[string]interface{} is central to the interface defined by Mango (https://github.com/paulbellamy/mango).


Brad Fitzpatrick

unread,
Jun 28, 2011, 1:09:30 AM6/28/11
to golan...@googlegroups.com
On Mon, Jun 27, 2011 at 10:07 PM, Islan Dberry <islan...@live.com> wrote:
> You could do something similar to what the http package
> does and create a Handler interface:
>
> type Handler interface {
>    Serve(w http.ResponseWriter, r *http.Request, v map[string]string)
> }

The problem with this approach is that the handlers don't compose with other handlers written to the http Handler interface. It would be nice if http.Request had a map[string]interface{} field where gorouter and other handlers can store arbitrary data associated with the request. 

If the following field is added to http.Request

    Env map[string]interface{}

I'm afraid it would just turn into a dumping ground.

Which I guess is kinda the point, which makes it even more scary.

I'd rather understand what's actually desired.

Islan Dberry

unread,
Jun 28, 2011, 1:55:30 AM6/28/11
to golan...@googlegroups.com
>>    Env map[string]interface{}
>
> I'm afraid it would just turn into a dumping ground.
>
> Which I guess is kinda the point, which makes it even
> more scary.

Are you concerned that independent developers will not standardize what goes in the map?

Brad Fitzpatrick

unread,
Jun 28, 2011, 10:25:15 AM6/28/11
to golan...@googlegroups.com
If it were standardized, they'd just be struct fields.

I think the whole point of it is to not standardize, right?

Islan Dberry

unread,
Jun 28, 2011, 11:10:31 AM6/28/11
to golan...@googlegroups.com
I think the whole point of it is to not standardize, right?

A standard can not and should not enumerate everything that an application might want to associate with a request. Example: The appengine.Context object can be passed through the map[string]interface{}, but it's not appropriate to declare the context object in the standard library.

Brad Fitzpatrick

unread,
Jun 28, 2011, 11:59:07 AM6/28/11
to golan...@googlegroups.com
On Tue, Jun 28, 2011 at 8:10 AM, Islan Dberry <islan...@live.com> wrote:
I think the whole point of it is to not standardize, right?

A standard can not and should not enumerate everything that an application might want to associate with a request. Example: The appengine.Context object can be passed through the map[string]interface{}, but it's not appropriate to declare the context object in the standard library.

Agreed.  But let's say you stick the App Engine Context in a map.  What key do you use?  "context"?  What if somebody else comes along with their own context?  Okay, so you name it "AppEngineContext".  But now the map isn't threadsafe.  One more thing to document, that you need to access your context and stuff it away before starting any goroutines. I guess you can argue that there are already maps in the Request object.  What about encapsulation?  Do you want everybody to have access to everything inside your map?  For an App Engine Context it's probably harmless, but if you're passing around innards that others shouldn't be touching (but might be tempted to for convenience), then you can't refactor safely because you're not sure who's access what.

And it means the http package can never refactor because there's always this grab bag we have to maintain, just in case.

Rather than be a hater, let me propose an alternative.

If you had a type like:

type RequestVariable struct {
        lk sync.Mutex
        m  map[*http.Request]interface{}
}

With an implementation something like:

func (v *RequestVariable) Set(req *http.Request, val interface{}) {
        v.lk.Lock()
        defer v.lk.Unlock()
        if v.m == nil {
                v.m = make(map[*http.Request]interface{})
        }
        v.m[req] = val
}

func (v *RequestVariable) Clear(req *http.Request) {
        v.lk.Lock()
        defer v.lk.Unlock()
        v.m[req] = nil, false
}

func (v *RequestVariable) Get(req *http.Request) interface{} {
        v.lk.Lock()
        defer v.lk.Unlock()
        if v.m == nil {
                return nil
        }
        return v.m[req]
}

Then whenever you need a grab bag variable, you could make it a proper variable, choose to export it, share it, whatever:
 
var appEngineContext = new(RequestVariable)

And in your handlers or subhandlers:

func main() {
        http.ListenAndServe(":8080", http.HandlerFunc(foo))
}

func foo(rw http.ResponseWriter, req *http.Request) {
        appEngineContext.Set(req, getAppEngineContext(req))
        defer appEngineContext.Clear(req)
        bar(rw, req)   // Note: not passing the context along.  bar might be multiple hops & packages away.
}

func bar(rw http.ResponseWriter, req *http.Request) {
        ctx := appEngineContext.Get(req)
        println("Got context:", ctx)
}

func getAppEngineContext(req *http.Request) interface{} {
        return 42 // whatever it does                                                                 
}


And now you have encapsulation, thread-safety, and it's safe to refactor.

Does that work for you, or am I missing requirements?

Russ Cox

unread,
Jun 28, 2011, 12:01:03 PM6/28/11
to golan...@googlegroups.com
If you need to pass another parameter to a handler,
change the type of function you allow to be registered
as a handler.

Russ

Islan Dberry

unread,
Jun 28, 2011, 1:08:41 PM6/28/11
to golan...@googlegroups.com
 What key do you use? 

A string naming convention is convenient and workable.  See WSGI, Rack, Ring, Java Servlets, ASP.net for examples where string names are used successfully.

If strings really are a problem, it's simple enough to define a function that returns unique key objects.

But now the map isn't threadsafe

There's no synchronization for the request in general. The bag of name value pairs does not make the problem any better or worse.

> What about encapsulation? 

Use unexported fields and methods to limit access to the objects placed in the map.

> the http package can never refactor because there's
> always this grab bag we have to maintain

What is an example of a refactoring that is prevented by attaching a grab bag to the request object?  It seems unlikely that the request object will go away or take on a different role.

Does that work for you, or am I missing requirements?

Yes, it works. I assume that this is a generalization of how the app engine context is managed. 

The only advantage that I see is that it's thread safe.  Is that really needed?  The disadvantage is that request variables are more complicated to use than a map.

Russ Cox

unread,
Jun 28, 2011, 1:20:52 PM6/28/11
to golan...@googlegroups.com
>> the http package can never refactor because there's
>> always this grab bag we have to maintain
> What is an example of a refactoring that is prevented by attaching a grab
> bag to the request object?  It seems unlikely that the request object will
> go away or take on a different role.

In your proposal, the map[string]interface{} becomes something
that we have to keep in the API forever. If someone imports
"http" it's not clear whether they're trying to use this magic map.

In Brad's proposal, the RequestVariable can be in your own
package. You can use it no matter what we do to the API
and we don't have to maintain your code for you.
It really seems like a great solution.

If you care a lot about having a map then you could
put a

func Env(req *http.Request) map[string]interface{}

in gorouter or whatever is dispatching the requests,
using Brad's code, and still not need to change package http.

Russ

Reply all
Reply to author
Forward
Message has been deleted
0 new messages