Skip to content

Commit

Permalink
Client examples (#1208)
Browse files Browse the repository at this point in the history
* examples/host_client

* examples/client

* examples/client/client.go refine imports

Co-authored-by: Erik Dubbelboer <erik@dubbelboer.com>

* examples/host_client/hostclient.go refine imports

Co-authored-by: Erik Dubbelboer <erik@dubbelboer.com>

Co-authored-by: Erik Dubbelboer <erik@dubbelboer.com>
  • Loading branch information
stokito and erikdubbelboer committed Feb 9, 2022
1 parent 6a3cc23 commit 632e222
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/client/.gitignore
@@ -0,0 +1 @@
client
6 changes: 6 additions & 0 deletions examples/client/Makefile
@@ -0,0 +1,6 @@
client: clean
go get -u github.com/valyala/fasthttp
go build

clean:
rm -f client
21 changes: 21 additions & 0 deletions examples/client/README.md
@@ -0,0 +1,21 @@
# Client Example

The Client is useful when working with multiple hostnames.

See the simplest `sendGetRequest()` for GET and more advanced `sendPostRequest()` for a POST request.

The `sendPostRequest()` also shows:
* Per-request timeout with `DoTimeout()`
* Send a body as bytes slice with `SetBodyRaw()`. This is useful if you generated a request body. Otherwise, prefer `SetBody()` which copies it.
* Parse JSON from response
* Gracefully show error messages i.e. timeouts as warnings and other errors as a failures with detailed error messages.

## How to build and run
Start a web server on localhost:8080 then execute:

make
./client

## Client vs HostClient
Internally the Client creates a dedicated HostClient for each domain/IP address and cleans unused after period of time.
So if you have a single heavily loaded API endpoint it's better to use HostClient. See an example in the [examples/host_client](../host_client/)
125 changes: 125 additions & 0 deletions examples/client/client.go
@@ -0,0 +1,125 @@
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"reflect"
"time"

"github.com/valyala/fasthttp"
)

var headerContentTypeJson = []byte("application/json")

var client *fasthttp.Client

type Entity struct {
Id int
Name string
}

func main() {
// You may read the timeouts from some config
readTimeout, _ := time.ParseDuration("500ms")
writeTimeout, _ := time.ParseDuration("500ms")
maxIdleConnDuration, _ := time.ParseDuration("1h")
client = &fasthttp.Client{
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
MaxIdleConnDuration: maxIdleConnDuration,
NoDefaultUserAgentHeader: true, // Don't send: User-Agent: fasthttp
DisableHeaderNamesNormalizing: true, // If you set the case on your headers correctly you can enable this
DisablePathNormalizing: true,
// increase DNS cache time to an hour instead of default minute
Dial: (&fasthttp.TCPDialer{
Concurrency: 4096,
DNSCacheDuration: time.Hour,
}).Dial,
}
sendGetRequest()
sendPostRequest()
}

func sendGetRequest() {
req := fasthttp.AcquireRequest()
req.SetRequestURI("http://localhost:8080/")
req.Header.SetMethod(fasthttp.MethodGet)
resp := fasthttp.AcquireResponse()
err := client.Do(req, resp)
fasthttp.ReleaseRequest(req)
if err == nil {
fmt.Printf("DEBUG Response: %s\n", resp.Body())
} else {
fmt.Fprintf(os.Stderr, "ERR Connection error: %s\n", err)
}
fasthttp.ReleaseResponse(resp)
}

func sendPostRequest() {
// per-request timeout
reqTimeout := time.Duration(100) * time.Millisecond

reqEntity := &Entity{
Name: "New entity",
}
reqEntityBytes, _ := json.Marshal(reqEntity)

req := fasthttp.AcquireRequest()
req.SetRequestURI("http://localhost:8080/")
req.Header.SetMethod(fasthttp.MethodPost)
req.Header.SetContentTypeBytes(headerContentTypeJson)
req.SetBodyRaw(reqEntityBytes)
resp := fasthttp.AcquireResponse()
err := client.DoTimeout(req, resp, reqTimeout)
fasthttp.ReleaseRequest(req)
if err == nil {
statusCode := resp.StatusCode()
respBody := resp.Body()
fmt.Printf("DEBUG Response: %s\n", respBody)
if statusCode == http.StatusOK {
respEntity := &Entity{}
err = json.Unmarshal(respBody, respEntity)
if err == io.EOF || err == nil {
fmt.Printf("DEBUG Parsed Response: %v\n", respEntity)
} else {
fmt.Fprintf(os.Stderr, "ERR failed to parse reponse: %s\n", err)
}
} else {
fmt.Fprintf(os.Stderr, "ERR invalid HTTP response code: %d\n", statusCode)
}
} else {
errName, known := httpConnError(err)
if known {
fmt.Fprintf(os.Stderr, "WARN conn error: %s\n", errName)
} else {
fmt.Fprintf(os.Stderr, "ERR conn failure: %s %s\n", errName, err)
}
}
fasthttp.ReleaseResponse(resp)
}

func httpConnError(err error) (string, bool) {
errName := ""
known := false
if err == fasthttp.ErrTimeout {
errName = "timeout"
known = true
} else if err == fasthttp.ErrNoFreeConns {
errName = "conn_limit"
known = true
} else if err == fasthttp.ErrConnectionClosed {
errName = "conn_close"
known = true
} else {
errName = reflect.TypeOf(err).String()
if errName == "*net.OpError" {
// Write and Read errors are not so often and in fact they just mean timeout problems
errName = "timeout"
known = true
}
}
return errName, known
}
1 change: 1 addition & 0 deletions examples/host_client/.gitignore
@@ -0,0 +1 @@
hostclient
6 changes: 6 additions & 0 deletions examples/host_client/Makefile
@@ -0,0 +1,6 @@
host_client: clean
go get -u github.com/valyala/fasthttp
go build

clean:
rm -f host_client
13 changes: 13 additions & 0 deletions examples/host_client/README.md
@@ -0,0 +1,13 @@
# Host Client Example

The HostClient is useful when calling an API from a single host.
The example also shows how to use URI.
You may create the parsed URI once and reuse it in many requests.
The URI has a username and password for Basic Auth but you may also set other parts i.e. `SetPath()`, `SetQueryString()`.

# How to build and run
Start a web server on localhost:8080 then execute:

make
./host_client

35 changes: 35 additions & 0 deletions examples/host_client/hostclient.go
@@ -0,0 +1,35 @@
package main

import (
"fmt"
"os"

"github.com/valyala/fasthttp"
)

func main() {
// Get URI from a pool
url := fasthttp.AcquireURI()
url.Parse(nil, []byte("http://localhost:8080/"))
url.SetUsername("Aladdin")
url.SetPassword("Open Sesame")

hc := &fasthttp.HostClient{
Addr: "localhost:8080", // The host address and port must be set explicitly
}

req := fasthttp.AcquireRequest()
req.SetURI(url) // copy url into request
fasthttp.ReleaseURI(url) // now you may release the URI

req.Header.SetMethod(fasthttp.MethodGet)
resp := fasthttp.AcquireResponse()
err := hc.Do(req, resp)
fasthttp.ReleaseRequest(req)
if err == nil {
fmt.Printf("Response: %s\n", resp.Body())
} else {
fmt.Fprintf(os.Stderr, "Connection error: %s\n", err)
}
fasthttp.ReleaseResponse(resp)
}

0 comments on commit 632e222

Please sign in to comment.