Why isn't this go HTTP server spawning a goroutine per request in Chrome 47?

tlovely picture tlovely · Dec 20, 2015 · Viewed 8.7k times · Source

Previously titled: How to spawn goroutine per HTTP request?

The code below is a simple HTTP server that echos the path back to the requester. I used it to test if the ListenAndServe method of the net/http package by default fires a goroutine to handle each request; which I discovered it doesn't. If I make three requests at the same time, the first takes 10 seconds to return, the second 20 (10 seconds after the first returns), and the third 30 seconds.

package main

import (
  "fmt"
  "net/http"
  "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
  time.Sleep(10000 * time.Millisecond)
  fmt.Fprint(w, r.URL.Path)
}

func main() {
  http.HandleFunc("/", handler)
  http.ListenAndServe(":8080", nil)
}

Basically what I want is the main goroutine to listen for an HTTP connection, then pass the reading of the request and the response to a handler spawned in another goroutine.

Could someone point me in the right direction for accomplishing this? Preferably using the net/http package in go.

UPDATE 12/21/15 08:46 AM MST

I conducted the same exact test you did using my web browser (chrome 47), five requests at the root of localhost:8080, and the results were roughly:

1st: 10 seconds
2nd: 20 seconds
3rd: 30 seconds
4th: 36 seconds     
5th: 38 seconds

So, hopefully, the folks who would otherwise down-vote my question would understand my confusion and why I made the assumption I did. I have no idea why I got the results I did on the 4th and 5th request.

I ran the same test using curl and had the same results as @tomasz.

I'm using go1.2.1.

UPDATE 12/21/15 02:08 PM MST

Per @tomasz's suggestion below, I retitled this question from "How to spawn goroutine per HTTP request" to "Why isn't this go HTTP server spawning a goroutine per request in Chrome 47?"

Answer

tomasz picture tomasz · Dec 20, 2015

All is good, your handler is run in a separate routine for each request. Take a look at the source code of http.Server.Serve method. The last line in accepting loop says:

go c.serve()

The problem is probably with your testing. If you check the behaviour through the multiple tabs in browser the requests for matching URLs are probably queued instead of running them simultaneously (i.e. your client is not using "routines", not the server).

Try two different browsers or just use command line and, say, curl to test requests in parallel. For example (with some help from bash):

$ for i in {1..5}; do time curl localhost:8080 &; done
# after ignoring some mess...
curl localhost:8080  0.00s user 0.00s system 0% cpu 10.013 total
curl localhost:8080  0.00s user 0.00s system 0% cpu 10.014 total
curl localhost:8080  0.00s user 0.00s system 0% cpu 10.012 total
curl localhost:8080  0.00s user 0.00s system 0% cpu 10.019 total

You server works like a charm.

Update

I can confirm this behaviour on Chrome 47, but also noticed you can open multiple tabs with, say, http://localhost:8080/test1, http://localhost:8080/test2 etc. and you will get expected results. That indicates there's indeed some queuing mechanism in Chrome for matching URLs.