Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client examples #1208

Merged
merged 4 commits into from Feb 9, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}