Files

110 lines
2.0 KiB
Go

package icapclient
import (
"bytes"
"context"
"errors"
"io"
"net"
"sync"
"time"
)
// ICAPConnConfig is the configuration for the icap connection.
type ICAPConnConfig struct {
// Timeout is the maximum amount of time a connection will be kept open
Timeout time.Duration
}
// ICAPConn manages the transport layer for ICAP protocol.
type ICAPConn struct {
tcp net.Conn
mu sync.Mutex
timeout time.Duration
}
// NewICAPConn creates a new connection configuration.
func NewICAPConn(conf ICAPConnConfig) (*ICAPConn, error) {
return &ICAPConn{
timeout: conf.Timeout,
}, nil
}
// Connect connects to the ICAP server.
func (c *ICAPConn) Connect(ctx context.Context, address string) error {
dialer := net.Dialer{Timeout: c.timeout}
conn, err := dialer.DialContext(ctx, "tcp", address)
if err != nil {
return err
}
c.tcp = conn
if c.timeout > 0 {
deadline := time.Now().Add(c.timeout)
if err := c.tcp.SetDeadline(deadline); err != nil {
return err
}
}
return nil
}
// Send sends a request to the ICAP server and reads the response.
func (c *ICAPConn) Send(in []byte) ([]byte, error) {
if !c.ok() {
return nil, ErrInvalidConnection
}
c.mu.Lock()
defer c.mu.Unlock()
_, err := c.tcp.Write(in)
if err != nil {
return nil, err
}
var data []byte
buf := make([]byte, 4096)
for {
n, err := c.tcp.Read(buf)
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
if errors.Is(err, io.EOF) || n == 0 {
break
}
data = append(data, buf[:n]...)
// Protocol checks for message termination
{
if bytes.Equal(data, []byte(icap100ContinueMsg)) {
break
}
if bytes.HasSuffix(data, []byte(doubleCRLF)) {
break
}
if bytes.Contains(data, []byte(icap204NoModsMsg)) {
break
}
}
}
return data, nil
}
// Close closes the TCP connection.
func (c *ICAPConn) Close() error {
if !c.ok() {
return ErrInvalidConnection
}
return c.tcp.Close()
}
func (c *ICAPConn) ok() bool {
return c != nil && c.tcp != nil
}