go-ldap / ldap

Basic LDAP v3 functionality for the GO programming language.
Other
2.25k stars 355 forks source link

Connection pooling in Dial to server #383

Open mirza-ali-ctct opened 2 years ago

mirza-ali-ctct commented 2 years ago

Is it possible to allow connection in the Dial call or is there an equivalent function for connection pooling when making ldap calls?

cpuschma commented 2 years ago

As of now, there's no method implemented for connection pooling. You can setup a simple connection pool yourself with just an array or even a channel.

The one thing that it hard to manage is to determine when a connection has been abandoned by the directory server. On Active Directory for example, when enabled the server can simply close the underyling TCP connection. This ends the reader loop, which then triggers the deferred function to call conn.Close().

To put it simply: Before using a pooled connection, run conn.IsClosing() to see if the connection is already rendered unusable. Here's a very simple, untested way you could try:

package main

import (
    "fmt"
    "github.com/go-ldap/ldap"
    "log"
    "sync"
)

// This channel will later hold all pooled connections
// 1024 is the max. number of objects the channel can hold
// until it blocks.
var connectionPool = make(chan *ldap.Conn, 1024)

func getConnection(dialURL string) (conn *ldap.Conn, err error) {
    select {
    case conn = <-connectionPool:
        if conn.IsClosing() {
            // The connection is already closing and unusable at this point
            // Retry one more time
            return getConnection(dialURL)
        }
    default:
        // When there's no object to retrieve from the channel, create
        // a new one
        conn, err = ldap.DialURL(dialURL)
    }

    return
}

func putConnection(conn *ldap.Conn) {
    select {
    case connectionPool <- conn:
        // Try to put the connection back into the pool
    default:
        // Connection pool is full, close and discard connection
        fmt.Println("Connection closed since pool is full")
        conn.Close()
    }
}

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(5)

    for n := 0; n < 5; n++ {
        go func(n int) {
            for i := 0; i < 1024; i++ {
                search()
            }
            wg.Done()
        }(n)
    }

    wg.Wait()
    log.Printf("%#v len: %d", connectionPool, len(connectionPool))
}

func search() {
    conn, err := getConnection("ldap://192.168.1.1:389")
    if err != nil {
        log.Fatalln(err)
    }
    defer putConnection(conn)

    conn.Bind("administrator@pusch.local", "")
    type User struct {
        DN                 string `ldap:"dn"`
        CN                 string `ldap:"cn"`
        UserAccountControl int    `ldap:"userAccountControl"`
    }

    result, err := conn.Search(&ldap.SearchRequest{
        BaseDN:       "dc=pusch,dc=local",
        Scope:        ldap.ScopeWholeSubtree,
        DerefAliases: ldap.NeverDerefAliases,
        Filter:       "(mail=john.doe@pusch.local)",
        Attributes:   []string{"cn", "mail", "userAccountControl"},
    })
    if err != nil {
        log.Fatalln(err)
    }

    targetUser := &User{}
    if err = result.Entries[0].Unmarshal(targetUser); err != nil {
        log.Fatalln(err)
    }
}
eryajf commented 2 years ago

Looking forward to progress in this regard

mirza-ali-ctct commented 2 years ago

Thanks @eryajf . You can close this request

eryajf commented 2 years ago

You can close this request

Is there any elegant plan?

eryajf commented 2 years ago

@mirza-ali-ctct Now, you can try this. https://github.com/eryajf/ldapool