-2

I made a small web server using gorilla/mux.

I took the small example at the end of the README file in their Github page.

r := mux.NewRouter()
r.HandleFunc("/", listUrlsHandler)
r.HandleFunc("/foo", fooHandler)
http.Handle("/", r)

err := http.ListenAndServe(":9090", nil) // set listen port
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

To emulate a long processing I added a timer in the callback function.

func fooHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintln(w, "foo is downloading...")
    log.Println("foo is downloading...")
    time.Sleep(15*time.Second)
}

I run 2x /foo at the same time:

2018/01/12 09:15:03 foo is downloading...
2018/01/12 09:15:18 foo is downloading...
  • Only one is processing, the second one is not called. Once the first one is returned (15sec later), the second one starts.
  • Why? I want them to be processed in parallel...
  • This is deal-breaker, that means clients cannot access to the same page at the same moment.

How can I reply to the client and process later? Adding a goroutine in the handler?

func fooHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintln(w, "foo is downloading...")
    log.Println("foo is downloading...")
    go time.Sleep(15*time.Second) // HERE <------------
    // go myCustomFunc()          // HERE <------------
}

I had an idea of how it's working but obviously I am totally wrong. I thought the router created a goroutine at each call.

Could you tell me what's the best practice to do what I want ?

I found the problem using tcp dump:

The second request opens a TCP connection that gets closed 150ms later (FIN from client). Then the second request is processed by the first TCP connection opened by the first request after the first request is done.

Might be Firefox's behavior that close the TCP connection and use the previous connection.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Alexis
  • 2,136
  • 2
  • 19
  • 47
  • A technical point, but it's actually the `http.ListenAndServe` (which is provided by Go itself) that is allowing you to serve a website / application -- not gorilla/mux. Gorilla is providing a mechanism to route incoming requests to go functions you provide. – Dan Esparza Jan 12 '18 at 00:12
  • The net/http server creates a goroutine per connection. The Gorilla mux does not start or manage goroutines. It simply calls through to the handler on whichever goroutine it was called on. A request does not complete until the handler returns. Sleeping 15 seconds causes the client to wait 15 seconds. Don't sleep. You might be observing the browser throttling requests to a host (and this has nothing to do with server). – Charlie Tumahai Jan 12 '18 at 00:18
  • @Cerise Limón: I edited my post, can I use a goroutine for this ? What the best practice for heavy processing request ? But still, that means only one client can access to the same page at the same time... It doesn't create a goroutine for my second connection. – Alexis Jan 12 '18 at 00:24
  • 1
    The best practice is to process the request on goroutine created by the net/http server. If the application has work to do after the response is written, then start a goroutine to execute that work. That will free the goroutine to process the next request on the connection. – Charlie Tumahai Jan 12 '18 at 00:28
  • I am very surprised that Go is working like that. Only one goroutine per route... Why not using one goroutine per connection instead ? – Alexis Jan 12 '18 at 00:37
  • 1
    [The net/http server starts one goroutine per connection](https://github.com/golang/go/blob/c13e0e8ceed4395e9ec8176579346ca6a5db269f/src/net/http/server.go#L2795), not one per route. – Charlie Tumahai Jan 12 '18 at 00:49
  • I might silly then, I don't understand why I cannot access twice at the same time `/foo` then. If there is one goroutine per `/foo` access, I shouldn't wait for 15sec to see the second one. – Alexis Jan 12 '18 at 00:51
  • 1
    The issue is on the client side. Show the code or describe how you are requesting the resource. – Charlie Tumahai Jan 12 '18 at 01:17
  • I just open 2 tabs in Firefox/Chrome and I send `http://localhost:9090/foo` in both tabs. Ok I see, it might be because my requests go through the `loopback`. Thank you very much, I understand now, I am reassured. – Alexis Jan 12 '18 at 01:44
  • 1
    The browser stalls when requesting the page the second time. See https://stackoverflow.com/questions/29206067/understanding-chrome-network-log-stalled-state for info. – Charlie Tumahai Jan 12 '18 at 03:40

1 Answers1

1

If you add a log call to your handler you can see when responses are being processed:

func downloadFileHandler(w http.ResponseWriter, r *http.Request) {
   vars := mux.Vars(r)
   w.WriteHeader(http.StatusOK)
   fmt.Fprintf(w, "%s is downloading...\n", vars["namefile"])
   log.Printf("%s is downloading...\n", vars["namefile"])
   time.Sleep(15*time.Second)
}

Eg:

2018/01/12 10:52:42 foo is downloading...
2018/01/12 10:52:44 bar is downloading...

Both calls are being processed synchronously due to Gorilla Mux using Go's underlying go routines but the 15-second time.Sleep is preventing you from seeing the results.

t j
  • 7,026
  • 12
  • 46
  • 66
  • The problem is you are using 2 different routes `foo` and `bar`, no problem for this. But how about `foo` and `foo`: they are processed one after another: `2018/01/12 09:04:54 foo is downloading... 2018/01/12 09:05:09 foo is downloading...` That means only one client can access to the same page?! It's a deal-breaker for me, I guess I did something wrong in the implementation. – Alexis Jan 12 '18 at 00:08