diff --git a/examples/client/.gitignore b/examples/client/.gitignore new file mode 100644 index 0000000000..b051c6c57f --- /dev/null +++ b/examples/client/.gitignore @@ -0,0 +1 @@ +client diff --git a/examples/client/Makefile b/examples/client/Makefile new file mode 100644 index 0000000000..d2844fb6e0 --- /dev/null +++ b/examples/client/Makefile @@ -0,0 +1,6 @@ +client: clean + go get -u github.com/valyala/fasthttp + go build + +clean: + rm -f client diff --git a/examples/client/README.md b/examples/client/README.md new file mode 100644 index 0000000000..cef2d35361 --- /dev/null +++ b/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/) diff --git a/examples/client/client.go b/examples/client/client.go new file mode 100644 index 0000000000..0bb66f08f3 --- /dev/null +++ b/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 +} diff --git a/examples/host_client/.gitignore b/examples/host_client/.gitignore new file mode 100644 index 0000000000..097652f075 --- /dev/null +++ b/examples/host_client/.gitignore @@ -0,0 +1 @@ +hostclient diff --git a/examples/host_client/Makefile b/examples/host_client/Makefile new file mode 100644 index 0000000000..161ab4454a --- /dev/null +++ b/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 diff --git a/examples/host_client/README.md b/examples/host_client/README.md new file mode 100644 index 0000000000..e40b3976ea --- /dev/null +++ b/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 + diff --git a/examples/host_client/hostclient.go b/examples/host_client/hostclient.go new file mode 100644 index 0000000000..fd1d6910c1 --- /dev/null +++ b/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) +}