Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ env:
- GO111MODULE=ON

go:
- 1.12.x
- 1.21.x

git:
depth: 1
Expand Down
51 changes: 20 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,64 +19,53 @@ go get -u github.com/egirna/icap-client

```go
import ic "github.com/egirna/icap-client"

```

**Making a simple RESPMOD call**

```go

req, err := ic.NewRequest(ic.MethodRESPMOD, "icap://<host>:<port>/<path>", httpReq, httpResp)

req, err := ic.NewRequest(context.Background(), MethodRESPMOD, "icap://<host>:<port>/<path>", httpReq, httpResp)
if err != nil {
log.Fatal(err)
}

client := &ic.Client{
Timeout: 5 * time.Second,
}
client, err := ic.NewClient(
ic.WithICAPConnectionTimeout(5 * time.Second),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Do(req)

if err != nil {
log.Fatal(err)
}

if err != nil {
log.Fatal(err)
}
```

**Note**: ``httpReq`` & ``httpResp`` here are ``*http.Response`` & ``*http.Request`` respectively
**Note**: `httpReq` & `httpResp` here are `*http.Response` & `*http.Request` respectively

**Setting preview obtained from OPTIONS call**

```go

optReq, err := ic.NewRequest(ic.MethodOPTIONS, "icap://<host>:<port>/<path>", nil, nil)

req, err := ic.NewRequest(context.Background(), ic.MethodOPTIONS, "icap://<host>:<port>/<path>", nil, nil)
if err != nil {
log.Fatal(err)
return
}

client := &ic.Client{
Timeout: 5 * time.Second,
client, err := ic.NewClient(
ic.WithICAPConnectionTimeout(5 * time.Second),
)
if err != nil {
log.Fatal(err)
}

req.SetPreview(optReq.PreviewBytes)

// do something with req(ICAP *Request)

```

**DEBUG Mode**

Turn on debug mode to inspect detailed & verbose logs to debug your code during development

```go
ic.SetDebugMode(true)

```

By default the icap-client will dump the debugging logs to the standard output(stdout), but you can always add your custom writer
By default, the icap-client will dump the debugging logs to the standard output(stdout),
but you can always add your custom writer

```go
f, _ := os.OpenFile("logs.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
Expand All @@ -88,7 +77,7 @@ For more details, see the [docs](https://godoc.org/github.com/egirna/icap-client

### Contributing

This package is still WIP, so totally open to suggestions. See the contributions guide [here](CONTRIBUTING.md).
This package is still WIP, so totally open to suggestions. See the contribution guide [here](CONTRIBUTING.md).

### License

Expand Down
128 changes: 47 additions & 81 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,115 +1,81 @@
package icapclient

import (
"bufio"
"bytes"
"errors"
"net/http"
"strconv"
"time"
"strings"
)

// Client represents the icap client who makes the icap server calls
// Client represents the ICAP client who makes the ICAP server calls
type Client struct {
scktDriver *Driver
Timeout time.Duration
config Config // Store config for connection parameters
}

// Do makes does everything required to make a call to the ICAP server
func (c *Client) Do(req *Request) (*Response, error) {

if c.scktDriver == nil { // create a new socket driver if one wasn't explicitly created
port, err := strconv.Atoi(req.URL.Port())

if err != nil {
return nil, err
}
c.scktDriver = NewDriver(req.URL.Hostname(), port)
// NewClient creates a new ICAP client (no persistent connection)
func NewClient(options ...ConfigOption) (Client, error) {
config := DefaultConfig()
for _, option := range options {
option(&config)
}
return Client{config: config}, nil
}

c.setDefaultTimeouts() // assinging default timeouts if not set already

if req.ctx != nil { // connect with the given context if context is set
if err := c.scktDriver.ConnectWithContext(*req.ctx); err != nil {
return nil, err
}
} else {
if err := c.scktDriver.Connect(); err != nil {
return nil, err
}
// Do make the ICAP request, creating and dropping a connection each time
func (c Client) Do(req Request) (res Response, err error) {
conn, err := NewICAPConn(c.config.ICAPConn)
if err != nil {
return Response{}, err
}

defer c.scktDriver.Close() // closing the socket connection

req.SetDefaultRequestHeaders() // assigning default headers if not set already

logDebug("The request headers: ")
dumpDebug(req.Header)
if err := conn.Connect(req.ctx, req.URL.Host); err != nil {
return Response{}, err
}
defer func() {
err = errors.Join(err, conn.Close())
}()

d, err := DumpRequest(req) // getting the byte representation of the ICAP request
req.setDefaultRequestHeaders()

message, err := toICAPRequest(req)
if err != nil {
return nil, err
return Response{}, err
}

if err := c.scktDriver.Send(d); err != nil { // sending the entire TCP message of the ICAP client to the server connected
return nil, err
// send the ICAP message to the server
dataRes, err := conn.Send(message)
if err != nil {
return Response{}, err
}

resp, err := c.scktDriver.Receive() // taking the response

res, err = toClientResponse(bufio.NewReader(strings.NewReader(string(dataRes))))
if err != nil {
return nil, err
return Response{}, err
}

if resp.StatusCode == http.StatusContinue && !req.bodyFittedInPreview && req.previewSet { // this block suggests that the ICAP request contained preview body bytes and whole body did not fit in the preview, so the serber responded with 100 Continue and the client is to send the remaining body bytes only
logDebug("Making request for the rest of the remaining body bytes after preview, as received 100 Continue from the server...")
return c.DoRemaining(req)
// check if the message is fully done scanning or if it needs to be sent another chunk
done := !(res.StatusCode == http.StatusContinue && !req.bodyFittedInPreview && req.previewSet)
if done {
return res, nil
}

return resp, nil
}

// DoRemaining requests an ICAP server with the remaining body bytes which did not fit in the preview in the original request
func (c *Client) DoRemaining(req *Request) (*Response, error) {

// get the remaining body bytes
data := req.remainingPreviewBytes

if !bodyAlreadyChunked(string(data)) { // if the body is not already chunke, then add the basic hexa body bytes notation
ds := string(data)
addHexaBodyByteNotations(&ds)
data = []byte(ds)
if !bodyIsChunked(string(data)) {
data = []byte(addHexBodyByteNotations(string(data)))
}

if err := c.scktDriver.Send(data); err != nil {
return nil, err
// hydrate the ICAP message with closing doubleCRLF suffix
if !bytes.HasSuffix(data, []byte(doubleCRLF)) {
data = append(data, []byte(crlf)...)
}

resp, err := c.scktDriver.Receive()

// send the remaining body bytes to the server
dataRes, err = conn.Send(data)
if err != nil {
return nil, err
return Response{}, err
}

return resp, nil
}

// SetDriver sets a new socket driver with the client
func (c *Client) SetDriver(d *Driver) {
c.scktDriver = d
}

func (c *Client) setDefaultTimeouts() {
if c.Timeout == 0 {
c.Timeout = defaultTimeout
}

if c.scktDriver.DialerTimeout == 0 {
c.scktDriver.DialerTimeout = c.Timeout
}

if c.scktDriver.ReadTimeout == 0 {
c.scktDriver.ReadTimeout = c.Timeout
}

if c.scktDriver.WriteTimeout == 0 {
c.scktDriver.WriteTimeout = c.Timeout
}
return toClientResponse(bufio.NewReader(strings.NewReader(string(dataRes))))
}
Loading