3

Let's suppose client uploads heavy image by http with slow internet connection, he opens connection to server, starts to write data and suddenly he loses this connection because of network problems. How can I detect this on server if handler function was not yet called. The solution I found is to check the connection state. But the problem is that it's not scalable, because a lot of goroutines will interact with global variable. Are there any more elegant solutions?

package main

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

// current state of connections
var connStates = make(map[string]http.ConnState)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("got new request")
        fmt.Println(connStates)
    })

    srv := &http.Server{
        Addr: ":8080",
        ConnState: func(conn net.Conn, event http.ConnState) {
            if event != http.StateClosed {
                connStates[conn.RemoteAddr().String()] = event
            } else {
                if connStates[conn.RemoteAddr().String()] == http.StateActive {
                    fmt.Println("client cancelled request")
                }
                delete(connStates, conn.RemoteAddr().String())
            }
        },
    }
    log.Fatal(srv.ListenAndServe())
}

  • What are you hoping to do with the connection state? Maybe if we had a better idea of what you are wanting to do with it we may have some better ideas. As for what you have, the only improvement I would make is to implement a `sync.Map` (https://golang.org/pkg/sync/#Map) instead of a regular `map`. – Jacob Lambert Apr 28 '19 at 03:40
  • What am I doing with connection state? As u see I have a global map to keep track of all connections, remote address as key(because it's unique for each connection) and connection state as value. So I added `ConnState` callback to server, which will be called each time state of connection is changed, using it I support this map of connections. And the main thing, detection of failed connection. As u see I check if current state is closed and the previous one was open - failed http connection, because there was no `http.StateActive` state. – Roman Alexandrovich Apr 28 '19 at 07:12
  • What I am asking is what are you ultimately wanting to do with this information? Are you just logging it? Are you wanting to perform a time sensitive operation based upon the state or can what you are wanting to do be delayed a bit? I can see that you are presently just logging. – Jacob Lambert Apr 28 '19 at 13:53
  • yeah, I just wanna log such event. – Roman Alexandrovich Apr 28 '19 at 14:46

2 Answers2

1

You could use context within your handler, for example, this would detect when the client disconnects and return and http.StatusPartialContent besides calling someCleanup() in where you could have your logging logic.

https://play.golang.org/p/5Yr_HBuyiZW

func helloWorld(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    ch := make(chan struct{})

    go func() {
            time.Sleep(5 * time.Second)
            fmt.Fprintln(w, "Hello World!")
            ch <- struct{}{}
    }()

    select {
    case <-ch:
    case <-ctx.Done():
            http.Error(w, ctx.Err().Error(), http.StatusPartialContent)
            someCleanUP()
    }

}

nbari
  • 25,603
  • 10
  • 76
  • 131
  • The main problem with ur solution, that handler is going to be called only when user has finished writing data to connection and waiting for response, so u will be able to detect case, when user has disconnected in time when server processes request, but not in time when user is writing data to opened connection and actually this is what I need to detect. But anyway, ur code snippet is really useful, thank u. – Roman Alexandrovich Apr 29 '19 at 13:43
  • I only do not understand why did u pass channel as argument to handler function, can u clarify this please? – Roman Alexandrovich Apr 29 '19 at 13:57
  • Hi @RomanAlexandrovich, this could explain it better https://stackoverflow.com/a/30183888/1135424, however, in this case, it can be omitted, I updated the answer – nbari Apr 29 '19 at 14:17
0

If you only need to have logs you can even simplify the code:

srv := &http.Server{
        Addr: ":8080",
        ConnState: func(conn net.Conn, event http.ConnState) {
            log.Printf("addr: %s, changed state to: %s", conn.RemoteAddr(), event.String())
        },
    }

That callback will be triggered on each change of the conn