1

I want to map path names to files that live at a different location. But my code serves /path/c.txt when I visit /a, instead of /path/a.txt. What am I doing wrong?

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fileRoutes := map[string]string {
        "/a": "/path/a.txt",
        "/b": "/path/b.txt",
        "/c": "/path/c.txt",
    }

    for req, path := range fileRoutes {
        http.HandleFunc(req, func(w http.ResponseWriter, r *http.Request) {
            fmt.Printf("actual request: %v\n", r.URL.Path)
            fmt.Printf("registered request: %v\n", req)

            r.URL.Path = path

            fmt.Println(req)
            fmt.Println(path)

            // serveFile(w, r)
        })
    }

    http.ListenAndServe(":8004", nil)
}

This sample code produces:

actual request: /a
registered request: /c
/c
/path/c.txt

Thank you!

Nuwan Alawatta
  • 1,812
  • 21
  • 29
stef
  • 313
  • 2
  • 12

2 Answers2

3

The problem is that all the handle functions are accessing the same req and path that is set to the last variable of the loop after the loop, which are /c and /path/c.txt.

To fix the problem, add req,path := req,path before http.HandleFunc(...).

It seems weird. What it does is to declare new req and path in the scope of loop, which is new every iteration, so every closure binds to different value.

leaf bebop
  • 7,643
  • 2
  • 17
  • 27
  • Or declare `req` and `path` as global variables! (`var req string` and `var path string` before starting to loop) – Daniel Schütte Jul 10 '18 at 01:31
  • @DanielSchuette No, that doesn't work. Try yourself. – leaf bebop Jul 10 '18 at 02:21
  • Works for me. You have to replace `:=` with `=` and re-assign a temporary variable in the loop, of course. My answer might have been to short to make that point, though. It is not really different from your solution, too. – Daniel Schütte Jul 10 '18 at 02:40
  • @DanielSchuette So you just promote those assigned in `for` statement to global scope. What's the point? – leaf bebop Jul 10 '18 at 03:16
  • That's how I usually do it, especially because it looks cleaner. But that is opinion-based, of course. I think it is easier for others to see WHY you reassign inside the for-loop if you use another variable. It's just an alternative, that's why I wrote this comment. – Daniel Schütte Jul 10 '18 at 16:12
0

Another solution would be the one used here (which I thanks to the answer now understand): https://github.com/golang/blog/blob/master/rewrite.go:

path := fileRoutes[req] before http.HandleFunc

stef
  • 313
  • 2
  • 12