A high-performance, production-ready LDAP connection pool library for Go, built on top of go-ldap.
English | 中文
- Connection Pooling: Efficient connection reuse to avoid connection limits
- TLS/SSL Support: Full support for LDAPS and StartTLS with custom configurations
- Context Support: Timeout and cancellation support for all operations
- Connection Management: Automatic cleanup of expired and idle connections
- Thread-Safe: Concurrent access with proper synchronization
- Health Monitoring: Built-in connection health checks and statistics
- Backward Compatible: Drop-in replacement for existing go-ldap usage
- Production Ready: Comprehensive error handling and logging
go get github.com/eryajf/ldapoolpackage main
import (
"context"
"fmt"
"log"
"time"
"github.com/eryajf/ldapool"
"github.com/go-ldap/ldap/v3"
)
func main() {
// Configure connection pool
config := ldapool.LdapConfig{
Url: "ldap://localhost:389",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 10, // Maximum connections
MaxIdle: 5, // Maximum idle connections
ConnTimeout: 30 * time.Second, // Connection timeout
ConnMaxLifetime: time.Hour, // Connection max lifetime
ConnMaxIdleTime: 30 * time.Minute, // Connection max idle time
}
// Create connection pool
pool, err := ldapool.NewPool(config)
if err != nil {
log.Fatal("Failed to create LDAP pool:", err)
}
defer pool.Close()
// Get connection with context
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := pool.GetConnection(ctx)
if err != nil {
log.Fatal("Failed to get connection:", err)
}
defer conn.Close() // Returns connection to pool
// Perform LDAP search
searchRequest := ldap.NewSearchRequest(
config.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectClass=person))",
[]string{"dn", "cn", "mail"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
log.Fatal("Search failed:", err)
}
// Process results
fmt.Printf("Found %d entries:\n", len(sr.Entries))
for _, entry := range sr.Entries {
fmt.Printf("DN: %s\n", entry.DN)
if cn := entry.GetAttributeValue("cn"); cn != "" {
fmt.Printf(" CN: %s\n", cn)
}
if mail := entry.GetAttributeValue("mail"); mail != "" {
fmt.Printf(" Email: %s\n", mail)
}
}
// Check pool statistics
open, idle := pool.Stats()
fmt.Printf("Pool stats: %d open, %d idle\n", open, idle)
}For existing code using simple connection management:
// Legacy API - still supported
config := ldapool.LdapConfig{
Url: "ldap://localhost:389",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 30,
}
conn, err := ldapool.Open(config)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Use conn for LDAP operations...config := ldapool.LdapConfig{
Url: "ldaps://ldap.example.com:636",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 10,
InsecureSkipVerify: false, // Verify certificates in production
}
pool, err := ldapool.NewPool(config)config := ldapool.LdapConfig{
Url: "ldap://ldap.example.com:389",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 10,
UseStartTLS: true,
InsecureSkipVerify: false,
}
pool, err := ldapool.NewPool(config)import "crypto/tls"
customTLS := &tls.Config{
ServerName: "ldap.example.com",
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
config := ldapool.LdapConfig{
Url: "ldaps://ldap.example.com:636",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 10,
TLSConfig: customTLS,
}| Option | Type | Default | Description |
|---|---|---|---|
Url |
string |
Required | LDAP server URL (ldap:// or ldaps://) |
BaseDN |
string |
Required | Base Distinguished Name |
AdminDN |
string |
Required | Admin bind DN |
AdminPass |
string |
Required | Admin password |
MaxOpen |
int |
10 |
Maximum open connections |
MaxIdle |
int |
5 |
Maximum idle connections |
ConnTimeout |
time.Duration |
30s |
Connection timeout |
ConnMaxLifetime |
time.Duration |
1h |
Maximum connection lifetime |
ConnMaxIdleTime |
time.Duration |
30m |
Maximum connection idle time |
TLSConfig |
*tls.Config |
nil |
Custom TLS configuration |
UseStartTLS |
bool |
false |
Use StartTLS to upgrade connection |
InsecureSkipVerify |
bool |
false |
Skip TLS certificate verification |
// Create pool with custom settings
pool, err := ldapool.NewPool(config)
if err != nil {
log.Fatal(err)
}
defer pool.Close()
// Get connection with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := pool.GetConnection(ctx)
if err != nil {
if err == context.DeadlineExceeded {
log.Println("Connection request timed out")
}
return
}
defer conn.Close()
// Check if connection is still valid
if conn.IsClosing() {
log.Println("Connection is being closed")
return
}
// Monitor pool health
open, idle := pool.Stats()
log.Printf("Pool health: %d open connections, %d idle", open, idle)conn, err := pool.GetConnection(ctx)
if err != nil {
switch err {
case ldapool.ErrPoolClosed:
log.Println("Connection pool is closed")
case context.DeadlineExceeded:
log.Println("Connection request timed out")
case context.Canceled:
log.Println("Connection request was canceled")
default:
log.Printf("Failed to get connection: %v", err)
}
return
}import "crypto/tls"
// Load client certificate
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
log.Fatal(err)
}
// Create TLS config with client certificate
tlsConfig := ldapool.NewClientCertTLSConfig("ldap.example.com", cert, false)
config := ldapool.LdapConfig{
Url: "ldaps://ldap.example.com:636",
BaseDN: "dc=example,dc=com",
AdminDN: "cn=admin,dc=example,dc=com",
AdminPass: "adminpass",
MaxOpen: 10,
TLSConfig: tlsConfig,
}| Scenario | MaxOpen | MaxIdle | ConnMaxLifetime | ConnMaxIdleTime |
|---|---|---|---|---|
| Low traffic | 5 | 2 | 1h | 30m |
| Medium traffic | 10 | 5 | 1h | 15m |
| High traffic | 20 | 10 | 30m | 5m |
| Very high traffic | 50 | 20 | 15m | 2m |
- Always use TLS in production environments
- Set appropriate timeouts to prevent hanging connections
- Monitor pool statistics for optimal sizing
- Use context with timeouts for all operations
- Implement proper error handling and retry logic
- Close connections properly to return them to the pool
# Run all tests
go test -v
# Run specific tests
go test -v -run "TestTLS"
go test -v -run "TestPool"
# Run with coverage
go test -v -coverCheck out the TLS_USAGE.md for comprehensive TLS configuration examples and security best practices.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- go-ldap - The underlying LDAP library
- RoninZc - Original core implementation
- ldapctl - Reference implementation for go-ldap usage
Need help? Open an issue or check existing issues for solutions to common problems.