how to apply netutil.LimitListener() to ListenAndServeTLS()'s listener? how to fetch ListenAndServeTLS()'s listener?

1,235 views
Skip to first unread message

David Marceau

unread,
Aug 2, 2016, 11:29:30 AM8/2/16
to golang-nuts
I found an example to limit the number of connections for a listener:
import "golang.org/x/net/netutil" //Within net/netutil/listen.go //func LimitListener(l net.Listener, n int) net.Listener connectionCount := 2 l, err := net.Listen("tcp", ":8000") if err != nil { log.Fatalf("Listen: %v", err) } defer l.Close() l = netutil.LimitListener(l, connectionCount) log.Fatal(http.Serve(l, nil))

BUT I'm not using net.Listen() and http.Serve(). I'm using the ListenAndServeTLS().

A closer look into ListenAndServeTLS:
func (srv *Server)
ListenAndServeTLS(certFile, keyFile string) error
{
...
tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) return srv.Serve(tlsListener)
}

How can I fetch the srv's tlsListener and then apply the netutil.LimitListener()?
ie.
netutil.LimitListener(srv.tlsListener, connectionCount)?

I'm doing my best not to tweak/recompile golang's sources to introduce this limitlistener feature to a tls server.
I was hoping someone had a trick to avoid that.

Thank you.

David Marceau


David Marceau

unread,
Aug 2, 2016, 11:46:19 AM8/2/16
to golang-nuts
Another question: Why wasn't tlsListener placed as part of the Server structure?
I think it makes sense to place it there since it is the server that creates and uses it.
It also makes sense to expose it to tweak it as some might more control over it and in this case the constraining the number of connections.
It seems we may current create as many connections as the operating system can handle, but isn't it more cautious to have something preventive
to limit the number of connections within the golang server itself or are you expecting system admins to limit the number of connections at the Operating System Level?

Tamás Gulácsi

unread,
Aug 3, 2016, 3:25:29 AM8/3/16
to golang-nuts
ListenAndServeTLS is just a convenience helper, feel free to copy and modify its contents, and use that function.

David Marceau

unread,
Aug 3, 2016, 8:03:03 AM8/3/16
to golang-nuts
I can understand at first glance we may perceive ListenAndServeTLS as a convenience helper, but if enough people to copy and paste its contents to modify it, then the sublety is
the function is actually assumed to be part of a Foundation/Framework that people build on top of. It is under the golang/net after all.  It was meant simply to be used and extended, NOT rewritten.

A framework usually has a manner in which to access all its class/duck-type's attributes and services.  In this case the package net has a number of duck-types, Client, Server.
Most of the Server's attributes are accessible, but stunningly the listener itself does not belong to the Server duck-type when I believe it should.  Otherwise the other suggestion would be to make it available by making it a return variable if other framework devs feel it shouldn't be part of the Server duck-type.  For those that don't want to tweak the listener, they can simply use the _ standard to ignore that return variable.

That said, I believe the listener is core and should be available from this one service.  I'm sure there are other developers that rely on ListenAndServeTLS and implicit understand exactly what it does.  I'm not asking to change anything in their understanding about its behaviour.  I asking the golang team to change something to in its characteristics which fundamentally belongs to the Server duck-type which extends its original meaning to something developers assume to be there in the first place.  I'm surprised
LimitListener(srv.tlsListener, connectionCount) is not part of the Server duck-type's api.  It makes sense to be there.  I'm stunned it's not there.
That's why I came to the forum to ask why it wasn't there the first place.

Nathan Kerr

unread,
Aug 3, 2016, 9:19:40 AM8/3/16
to golang-nuts
Your research revealed the essential lines from ListenAndServeTLS that basically say:

1. create a tls listener
2. have the server serve using that listener

The LimitListener example follows this same pattern, just with net.Listener instead of a tls.Listener. A careful reading reveals that ListenAndServeTLS does not do anything to http.Server that you cannot do from outside the http package. This means that you can implement it in your own code; changing it to fit your needs. Your code will probably flow something like:

1. get a listener
2. limit that listener
3. create a tls.Config
4. tlslistener on LimitedListener with config
5. Server.Serve(tlsListener)

I am not sure if it would be better to limit before or after the tls listener.

I call this trick expanding convenience functions and explain it in regards to another problem at http://pocketgophers.com/expanding-convenience-functions/ (http://pocketgophers.com/expanding-convenience-functions/).

To answer your other questions:

I think that tlsListener is not part of the Server structure (so that you could easily fetch it and limit it) is for two reasons. First, it keeps the server and the listener separate. There is no fundamental reason that a single server could not serve on multiple listeners.

Second, Serve is basically an infinite loop waiting on Listener.Accept. What would it mean to change the server's listener while it is blocked accepting a new connection? The old listener would be blocking. If a connection was never made to it, the new listener would never accept any connections.

The API does give the control you need, just not in the way you looked for it.

For that last question, controlling connections is usually done as a way to control the use of some other resource such as cpu, memory, database access, etc. Managing resources must be done at both the OS and server level. What needs to be done depends on what the server needs to do, what hardware it is running on, service level agreements, etc. Sometimes limiting, sometimes expanding limits, sometime increasing performance.

Hope this helps.

James Bardin

unread,
Aug 3, 2016, 9:28:09 AM8/3/16
to golang-nuts

The issue here in essence is that an http.Server doesn't store a new.Listener to expose, it only operates on one provided to the Serve method. Without changing the api, there's no way to expose a listener in an http.Server in a way that doesn't interfere with the other methods.

However, I also don't like that it's not completely trivial to create your own listener identical to that used by the http.Server. The basic http server isn't so bad, but does make use of a small tcpKeepAliveListener wrapper. The https method is a little more complicated, since it may modify the tls.Config. 

Maybe we could propose helper functions or methods for Listen and ListenTLS, which return a net.Listener? (I feel like there were related issues files, but I'm not finding them at the moment)

David Marceau

unread,
Aug 3, 2016, 4:41:18 PM8/3/16
to golang-nuts
I perused your blog entry you mentioned.  It's very interesting and will come in handy in the future.  Thank you. 

I can appreciate your point of view about accepting the fact that currently listeners are not part of the Server and just proceed to produce code and get it done ASAP.  My interim approach will require me to do as you suggested "expand the convenience function" as an entirely different function within my own package.

That said, if we:
1)change the Server duck-type's existing ListenAndServeTLS() contents a touch without affecting its API signature
2)add a few new properties to Server's structure
2.1)MaxListenersCount. The global max count of Listener we want.
2.2)MaxListenerPoolsCount.  The global max count of ListenerPool we want.
2.3)ListenerPools with the preferred type (ring, array, channel...)
2.4)Each ListenerPool would hold Listeners with the preferred type (ring, array, channel...)
3)When we add a listener to the Server, it would manage the balancing of the listeners across the pools while keeping to the constraints MaxListenersCount and MaxListenerPoolsCount.

This would prevent others from having to individually invest effort to "expand the convenience function" on the very same feature request: limiting connections to a tls server.

David Marceau

unread,
Aug 3, 2016, 6:27:18 PM8/3/16
to golang-nuts
I tried just what you mentioned.  Unfortunately even my interim solution when it is outside of the net.http package and within mine, there are many services that are not exported meaning I can't use them at all and the variables themselves are inaccessible.
I tried copying pasting some functions and tweaking them and I ended up going down a rabbit hole never to get out of.  The only way to achieve what I want would be to tweak the original golang sources for net.http.ListenAndServeTLS and stuff surrounding it.



On Wednesday, August 3, 2016 at 9:19:40 AM UTC-4, Nathan Kerr wrote:

Nathan Kerr

unread,
Aug 4, 2016, 10:45:54 AM8/4/16
to golang-nuts
I managed to expand ListenAndServeTLS and then apply LimitedListener.

My code and process are posted at https://pocketgophers.com/limit-https-listener/

tcpKeepAliveListener ended up being the only code that was copied but not specialized. It would have been nice if it were exported from the http package.

David Marceau

unread,
Aug 5, 2016, 11:55:31 AM8/5/16
to golang-nuts
I get what you're saying.  I did something similar, but modifying all those structures places you at risk of changing the entire default specified behaviour of the ListenAndServeTLS because you now not only introduced the limitConnections integer, but you in fact brought in other variables/functions that should have never been changed re-introduced in the first place.

I want to thank you for providing what you did.  It is still very useful.

That said I was hoping for something more deeply integrated within the current golang compiler itself like the following:
git diff
diff --git a/src/net/http/server.go b/src/net/http/server.go
index 89574a8..f135b57 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -29,6 +29,7 @@ import (
        "time"
 
        "golang_org/x/net/lex/httplex"
+       "golang_org/x/net/netutil"
 )
 
 // Errors used by the HTTP server.
@@ -2747,3 +2748,40 @@ func strSliceContains(ss []string, s string) bool {
        }
        return false
 }
+
+func (srv *Server) LimitListenAndServeTLS(certFile string, keyFile string, maxListeners int) error  {
+       addr := srv.Addr
+       if addr == "" {
+               addr = ":https"
+       }
+
+       // Setup HTTP/2 before srv.Serve, to initialize srv.TLSConfig
+       // before we clone it and create the TLS Listener.
+       if err := srv.setupHTTP2_ListenAndServeTLS(); err != nil {
+               return err
+       }
+
+       config := cloneTLSConfig(srv.TLSConfig)
+       if !strSliceContains(config.NextProtos, "http/1.1") {
+               config.NextProtos = append(config.NextProtos, "http/1.1")
+       }
+
+       configHasCert := len(config.Certificates) > 0 || config.GetCertificate != nil
+       if !configHasCert || certFile != "" || keyFile != "" {
+               var err error
+               config.Certificates = make([]tls.Certificate, 1)
+               config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+               if err != nil {
+                       return err
+               }
+       }
+
+       ln, err := net.Listen("tcp", addr)
+       if err != nil {
+               return err
+       }
+
+       tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
+       tlsListener = netutil.LimitListener(tlsListener, maxListeners)
+       return srv.Serve(tlsListener)
+}
Reply all
Reply to author
Forward
0 new messages