Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
6a3cc23
commit 632e222
Showing
8 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
client: clean | ||
go get -u github.com/valyala/fasthttp | ||
go build | ||
|
||
clean: | ||
rm -f client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
hostclient |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
host_client: clean | ||
go get -u github.com/valyala/fasthttp | ||
go build | ||
|
||
clean: | ||
rm -f host_client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |