You're absolutely right, Jacques!

I completely misunderstood this from the Go documentation 
<https://golang.org/ref/spec#Short_variable_declarations>:

Redeclaration does not introduce a new variable; it just assigns a new 
value to the original.

.. because I did not understand the difference between the *scope* of a 
variable and the *block* in which it was declared.

Thanks again for the help!

J.

On Friday, September 6, 2019 at 10:00:13 AM UTC-5, Jacques Supcik wrote:
>
> Yes! I think that your analysis is correct. The listener inside the if is 
> not the same as the one outside. You can check using fmt.Println(&listener) 
> and you will see two different addresses.
>
> -- Jacques
>
>
>
> Le vendredi 6 septembre 2019 15:52:11 UTC+2, jgr...@ou.edu a écrit :
>>
>> Ahh, I think I understand the problem!
>>
>> I assumed that "listener, err := ..." would use the existing listener 
>> variable 
>> (creating err on the spot), rather than creating a new locally-scoped 
>> listener?
>>
>> Therefore, as you said, listener is basically always nil outside that "if 
>> useListener>0 {}" scope? So server.ListenAndServe() is always invoked?
>>
>> If so, then - thanks a lot for the help! :)
>>
>> Cheers,
>>
>> J.
>>
>> On Friday, September 6, 2019 at 8:09:37 AM UTC-5, Jacques Supcik wrote:
>>>
>>> Hello,
>>>
>>> I tried your program and when you call listener, err := 
>>> net.Listen("tcp4", addrString) it does indeed only listen to "tcp4". 
>>> Later, when you call server.ListenAndServe() (because listener is nil), 
>>> the system seems to "re-listen" from the same port and then it listen to 
>>> "tcp" and not only "tcp4" (
>>> https://github.com/golang/go/blob/master/src/net/http/server.go#L2826)
>>>
>>> I don't understand why you call both "net.Listen" and 
>>> "server.ListenAndServe". If you want an HTTP server, I think that you can 
>>> just use "server.ListenAndServe" and you don't need anything more. If you 
>>> want an HTTP server and a generic TCP server, then you need 2 different 
>>> ports, one for the HTTP server and another for the generic TCP server. But 
>>> perhaps I did not understand your problem ;-)
>>>
>>> -- Jacques
>>>
>>>
>>> Le jeudi 5 septembre 2019 22:13:31 UTC+2, jgr...@ou.edu a écrit :
>>>>
>>>> Hi!
>>>>
>>>> I'm having some confusion over the behaviour of net.Listen() and it's 
>>>> interactions with http.Server.
>>>>
>>>> Can anyone take a look at this, and let me know what I'm doing wrong?
>>>>
>>>> Thanks!
>>>>
>>>>
>>>> System description
>>>> -------------------------
>>>>
>>>> Go: go version go1.12.9 darwin/amd64
>>>>
>>>> OS: macOS Mojave (10.14.6)
>>>>
>>>>
>>>> Problem description
>>>> --------------------------
>>>>
>>>> Passing a net.Listener from net.Listen() into the Serve() method of an 
>>>> http.Server does not behave how I expect ...
>>>>
>>>>
>>>> Test program
>>>> -----------------
>>>>
>>>> A simple server that responds to connections by echoing info regarding 
>>>> the URL by which it was contacted (see below):
>>>>
>>>> package main
>>>>
>>>> import (
>>>> "context"
>>>> "flag"
>>>> "fmt"
>>>> "log"
>>>> "net"
>>>> "net/http"
>>>> "os"
>>>> "os/signal"
>>>> "strconv"
>>>> "syscall"
>>>> "time"
>>>> )
>>>>
>>>> // Print information about the local machine's network interfaces
>>>> func printNetworkInterfaces() {
>>>> ifaces, err := net.Interfaces()
>>>> if err != nil { panic("net.Interfaces()") }
>>>>
>>>> if len(ifaces)<1 {
>>>> log.Println("No network interfaces found.")
>>>> return
>>>> }
>>>>
>>>> hostname, _ := os.Hostname()
>>>> log.Println( "Network interfaces for " + hostname )
>>>>
>>>> for _, iface := range ifaces {
>>>> addrs, err := iface.Addrs()
>>>> if err != nil { panic("iface.Addrs()") }
>>>> if len(addrs) < 1 { continue }
>>>> log.Println("-",iface.Name,iface.HardwareAddr)
>>>> for _, addr := range addrs {
>>>> switch v := addr.(type) {
>>>> case *net.IPNet:
>>>> str := fmt.Sprintf("IPNet: IP=%s, mask=%s, network=%s, string=%s", 
>>>> v.IP, v.Mask, v.Network(), v.String())
>>>> log.Println(" ", str)
>>>> case *net.IPAddr:
>>>> str := fmt.Sprintf("IPAddr: IP=%s, zone=%s, network=%s, string=%s", 
>>>> v.IP, v.Zone, v.Network(), v.String())
>>>> log.Println(" ", str)
>>>> default:
>>>> log.Println("<unknown>")
>>>> }
>>>> }
>>>> }
>>>> }
>>>>
>>>> // Just write the incoming url back to the sender
>>>> func echoHandler(w http.ResponseWriter, r *http.Request) {
>>>> txt := fmt.Sprintf("Echo: (%s)",r.URL.Path)
>>>> w.Write( []byte(txt+"\n") )
>>>> log.Println(txt)
>>>> }
>>>>
>>>> var (
>>>> listener_ = flag.Int("listener", 0, "Use an explicit net.Listener.")
>>>> port_     = flag.Int("port", 0, "Set the port to listen on (0 = any 
>>>> free port?).")
>>>> timeout_  = flag.Int("wait", 0, "Timout (in seconds) before server 
>>>> killed (0 = no timout).")
>>>> )
>>>>
>>>> func main() {
>>>>
>>>> onShutdown := func(what string, cleanup func()) {
>>>> log.Println( fmt.Sprintf("- Shutting down %s ...",what) )
>>>> cleanup()
>>>> log.Println( fmt.Sprintf("  %s shut down.",what) )
>>>> }
>>>>
>>>> flag.Parse()
>>>>
>>>> useListener := *listener_
>>>> port := *port_
>>>> timeout := *timeout_
>>>>
>>>> // Let's see what interfaces are present on the local machine
>>>>
>>>> printNetworkInterfaces()
>>>>
>>>> // Simple server for incoming connections.
>>>>
>>>> http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) 
>>>> {echoHandler(w,r)});
>>>>
>>>> var listener net.Listener = nil
>>>> addrString := fmt.Sprintf(":%d",port)
>>>>
>>>> // Using an explicit Listener provides more control over the specifics,
>>>> // e.g. tcp4/6 and letting the system select a currently free port.
>>>>
>>>> if useListener>0 {
>>>> log.Println("Using net.Listener")
>>>>
>>>> listener, err := net.Listen("tcp4", addrString) // :0 -> use any free 
>>>> port
>>>> if err != nil { log.Fatalln(err) }
>>>>
>>>> defer onShutdown("listener", func() {listener.Close()} )
>>>>
>>>> addrString = listener.Addr().String()
>>>> host, portStr, err := net.SplitHostPort(addrString) // as port may have 
>>>> been assigned by system
>>>> if err != nil { log.Fatalln(err) }
>>>>
>>>> log.Println( fmt.Sprintf("Listener Addr string: %s (host: %s, port: 
>>>> %s)",addrString,host,portStr) )
>>>>
>>>> port, err = strconv.Atoi(portStr)
>>>> if err != nil { log.Fatalln(err) }
>>>>
>>>> addrString = fmt.Sprintf(":%d",port) // as port may have been assigned 
>>>> by the system
>>>> }
>>>>
>>>> server := http.Server { Addr: addrString }
>>>>
>>>> // Run web server in a separate goroutine so it doesn't block our 
>>>> progress
>>>>
>>>> go func(server *http.Server, listener net.Listener) {
>>>>
>>>> var err error
>>>>
>>>> if listener == nil {
>>>> err = server.ListenAndServe()
>>>> } else {
>>>> err = server.Serve(listener)
>>>> }
>>>>
>>>> switch err {
>>>> case nil:
>>>> case http.ErrServerClosed:
>>>> log.Println("Caught ErrServerClosed")
>>>> default:
>>>> panic(err)
>>>> }
>>>> }(&server, listener)
>>>>
>>>> defer onShutdown("server", func() 
>>>> {server.Shutdown(context.Background())} )
>>>>
>>>> log.Println("Port:", port)
>>>> log.Println("Address:", addrString)
>>>>
>>>> // User interrupt channel
>>>>
>>>> sig := make(chan os.Signal, 1)
>>>> signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
>>>>
>>>> // Timeout channel, if needed
>>>>
>>>> tc := make(<-chan time.Time);
>>>> if timeout > 0 {
>>>> tc = time.After(time.Second * time.Duration(timeout))
>>>> }
>>>>
>>>> // Wait on user interrupt or timeout
>>>>
>>>> select {
>>>> case <-sig: // user interrupt
>>>> case <-tc: // timeout
>>>> }
>>>>
>>>> // Cleanup
>>>>
>>>> log.Println("Shutting down.")
>>>> }
>>>>
>>>> According to the Go docs <https://golang.org/pkg/net/#Listen>:
>>>>
>>>> For TCP networks, if the host in the address parameter is empty or a 
>>>> literal unspecified IP address, Listen listens on all available unicast 
>>>> and 
>>>> anycast IP addresses of the local system. To only use IPv4, use network 
>>>> "tcp4".
>>>>
>>>> It also says "See func Dial for a description of the network and 
>>>> address parameters", from which <https://golang.org/pkg/net/#Dial>:
>>>>
>>>> Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), 
>>>> "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), 
>>>> "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket".
>>>>
>>>> Therefore, I would expect that calling net.Listen("tcp4",":0") will 
>>>> listen on an (arbitrary) free port using all IPv4 interfaces. However, 
>>>> lsof indicates that it's listening for both IPv4 and IPv6 (I've masked 
>>>> some identifying information):
>>>>
>>>> me$ go run . --listener=1
>>>>
>>>> 2019/09/05 11:14:44 Network interfaces for XXXX
>>>> 2019/09/05 11:14:44 - lo0 
>>>> 2019/09/05 11:14:44   IPNet: IP=127.0.0.1, mask=ff000000, 
>>>> network=ip+net, string=127.0.0.1/8
>>>> 2019/09/05 11:14:44   IPNet: IP=::1, 
>>>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128
>>>> 2019/09/05 11:14:44   IPNet: IP=fe80::1, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64
>>>> 2019/09/05 11:14:44 - en0 x:x:x:x:x:x
>>>> 2019/09/05 11:14:44   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:14:44   IPNet: IP=10.195.66.129, mask=fffff800, 
>>>> network=ip+net, string=10.195.66.129/21
>>>> 2019/09/05 11:14:44 - utun0 
>>>> 2019/09/05 11:14:44   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:18:37 Using net.Listener
>>>> 2019/09/05 11:18:37 Listener Addr string: 0.0.0.0:53133 (host: 
>>>> 0.0.0.0, port: 53133)
>>>> 2019/09/05 11:14:44 Port: 53133
>>>> 2019/09/05 11:14:44 Address: :53133
>>>>
>>>> me, in another Terminal window$ lsof -i | grep LISTEN
>>>>
>>>> ARDAgent    360 me    9u  IPv6 0xd5f6fecc3cdf4c41      0t0  TCP 
>>>> *:net-assistant (LISTEN)
>>>> LogiVCCor   935 me    9u  IPv4 0xd5f6fecc47403101      0t0  TCP *:iims 
>>>> (LISTEN)
>>>> LogiVCCor   935 me   14u  IPv6 0xd5f6fecc3cdf40c1      0t0  TCP *:iims 
>>>> (LISTEN)
>>>> go_listen 14859 me    3u  IPv4 0xd5f6fecc451a7781      0t0  TCP *:53133 
>>>> (LISTEN)
>>>> go_listen 14859 me    5u  IPv6 0xd5f6fecc3cdf4681      0t0  TCP *:53133 
>>>> (LISTEN)
>>>>
>>>> Furthermore, it's only responding on localhost and [::1]; using any of 
>>>> the other interface addresses listed by net.Interfaces() fails to get 
>>>> a response:
>>>>
>>>> me$ time curl localhost:53133/localhost
>>>> Echo: (/localhost)
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [::1]:53133/[::1]
>>>> Echo: (/[::1])
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.005s
>>>>
>>>> me$ time curl 127.0.0.1:53133/127.0.0.1
>>>> ^C
>>>>
>>>> real 0m4.521s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [fe80::1]:53133/[fe80::1]
>>>> curl: (7) Couldn't connect to server
>>>>
>>>> real 0m0.014s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl 10.195.66.129:53133/10.195.66.129
>>>> ^C
>>>>
>>>> real 0m6.465s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [fe80::x:x:x:x]:53133/[fe80::x:x:x:x]
>>>> curl: (7) Couldn't connect to server
>>>>
>>>> real 0m0.013s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> It looks like trying to connect via (non-localhost) IPv4 addresses 
>>>> hangs on both lo0 and en0 (127.0.0.1, 10.195.66.129), and (non-[::1]) 
>>>> IPv6 addresses flat out refuse to connect.
>>>>
>>>> However, if we skip the use of a net.Listener, it looks like the 
>>>> http.Server only listens for IPv6:
>>>>
>>>> me$ go run .
>>>> 2019/09/05 11:26:38 Network interfaces for XXXX
>>>> 2019/09/05 11:26:38 - lo0 
>>>> 2019/09/05 11:26:38   IPNet: IP=127.0.0.1, mask=ff000000, 
>>>> network=ip+net, string=127.0.0.1/8
>>>> 2019/09/05 11:26:38   IPNet: IP=::1, 
>>>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128
>>>> 2019/09/05 11:26:38   IPNet: IP=fe80::1, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64
>>>> 2019/09/05 11:26:38 - en0 x:x:x:x:x:x
>>>> 2019/09/05 11:26:38   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:26:38   IPNet: IP=10.195.66.129, mask=fffff800, 
>>>> network=ip+net, string=10.195.66.129/21
>>>> 2019/09/05 11:26:38 - utun0 
>>>> 2019/09/05 11:26:38   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:26:38 Port: 0
>>>> 2019/09/05 11:26:38 Address: :0
>>>>
>>>> me, in another Terminal window$ lsof -i | grep LISTEN
>>>> ARDAgent    360 me    9u  IPv6 0xd5f6fecc3cdf4c41      0t0  TCP 
>>>> *:net-assistant (LISTEN)
>>>> LogiVCCor   935 me    9u  IPv4 0xd5f6fecc47403101      0t0  TCP *:iims 
>>>> (LISTEN)
>>>> LogiVCCor   935 me   14u  IPv6 0xd5f6fecc3cdf40c1      0t0  TCP *:iims 
>>>> (LISTEN)
>>>> go_listen 15016 me    3u  IPv6 0xd5f6fecc3cdf5201      0t0  TCP *:53170 
>>>> (LISTEN)
>>>>
>>>> This approach (i.e., ignoring net.Listener to simply use 
>>>> server.ListenAndServe()) seems to do a better job of listening on 
>>>> multiple interfaces/addresses:
>>>>
>>>> me$ time curl localhost:53170/localhost
>>>> Echo: (/localhost)
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [::1]:53170/[::1]
>>>> Echo: (/[::1])
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl 127.0.0.1:53170/127.0.0.1
>>>> Echo: (/127.0.0.1)
>>>>
>>>> real 0m0.014s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [fe80::1]:53170/[fe80::1]
>>>> curl: (7) Couldn't connect to server
>>>>
>>>> real 0m0.014s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl 10.195.66.129:53170/10.195.66.129
>>>> Echo: (/10.195.66.129)
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> me$ time curl [fe80::x:x:x:x]:53170/[fe80::x:x:x:x]
>>>> curl: (7) Couldn't connect to server
>>>>
>>>> real 0m0.015s
>>>> user 0m0.004s
>>>> sys 0m0.004s
>>>>
>>>> ... although it also has problems for IPv6 addresses other than ::1.
>>>>
>>>> If I use tcp instead of tcp4 in the call to net.Listen() (e.g. 
>>>> net.Listen("tcp", 
>>>> ":0")) I *always* get a bind error due to address already in use:
>>>>
>>>> me$ go run . --listener=1
>>>> 2019/09/05 11:45:13 Network interfaces for XXXX
>>>> 2019/09/05 11:45:13 - lo0 
>>>> 2019/09/05 11:45:13   IPNet: IP=127.0.0.1, mask=ff000000, 
>>>> network=ip+net, string=127.0.0.1/8
>>>> 2019/09/05 11:45:13   IPNet: IP=::1, 
>>>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128
>>>> 2019/09/05 11:45:13   IPNet: IP=fe80::1, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64
>>>> 2019/09/05 11:45:13 - en0 x:x:x:x:x:x
>>>> 2019/09/05 11:45:13   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:45:13   IPNet: IP=10.195.66.129, mask=fffff800, 
>>>> network=ip+net, string=10.195.66.129/21
>>>> 2019/09/05 11:45:13 - utun0 
>>>> 2019/09/05 11:45:13   IPNet: IP=fe80::x:x:x:x, 
>>>> mask=ffffffffffffffff0000000000000000, network=ip+net, 
>>>> string=fe80::x:x:x:x/64
>>>> 2019/09/05 11:45:13 Using net.Listener
>>>> 2019/09/05 11:45:13 Listener Addr string: [::]:53260 (host: ::, port: 
>>>> 53260)
>>>> 2019/09/05 11:45:13 Port: 53260
>>>> 2019/09/05 11:45:13 Address: :53260
>>>> panic: listen tcp :53260: bind: address already in use
>>>>
>>>> I get the same outcome if I use tcp6 in net.Listen(); I can only get 
>>>> the program to run using tcp4 which, on my machine at least, actually 
>>>> seems to open an additional IPv6 connection anyway - so I'm confused as to 
>>>> why net.Listen() doesn't seem to like tcp6.
>>>>
>>>> Despite the net.Listen() documentation directing you to net.Dial() 
>>>> docs for a discussion of the network parameter, some of these networks 
>>>> (e.g. ip, ip4, ip6) are unknown to net.Listen(). The documentation 
>>>> could be clearer in that respect! :)
>>>>
>>>> I can't figure out what's going on here. Any ideas what I might be 
>>>> doing wrong?
>>>>
>>>>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/e0eabef9-87f7-454f-ba12-85c4718b69a3%40googlegroups.com.

Reply via email to