# README
Gox Http
Gox Http provides utility to call a http endpoint. It provides following:
- Define all endpoint and api config in configuration file
- Circuit breaker using Hystrix
- Set concurrency for each api - this ensures that if we go beyond "concurrency" no of parallel requests then hystrix will reject the requests
- Set timeout for each api - the call will timeout if this request takes time > timeout defined
- acceptable_codes - list of "," separated status codes which are acceptable. These status codes will not be counted as errors and will not open hystrix circuit
How to use
Given below is a example on how to use this liberary
package main
import (
"context"
"fmt"
"github.com/devlibx/gox-base"
"github.com/devlibx/gox-base/serialization"
goxHttpApi "github.com/devlibx/gox-http/api"
"github.com/devlibx/gox-http/command"
"log"
)
// Here you can define your own configuration
// We have used "jsonplaceholder" as a test server. A api "getPosts" is defined which uses "server=jsonplaceholder"
var httpConfig = `
servers:
jsonplaceholder:
host: jsonplaceholder.typicode.com
port: 443
https: true
connect_timeout: 1000
connection_request_timeout: 1000
testServer:
host: localhost
port: 9123
apis:
getPosts:
method: GET
path: /posts/{id}
server: jsonplaceholder
timeout: 1000
acceptable_codes: 200,201
delay_timeout_10:
path: /delay
server: testServer
timeout: 10
concurrency: 3
`
func main() {
cf := gox.NewCrossFunction()
// Read config and
config := command.Config{}
err := serialization.ReadYamlFromString(httpConfig, &config)
if err != nil {
log.Println("got error in reading config", err)
return
}
// Setup goHttp context
goxHttpCtx, err := goxHttpApi.NewGoxHttpContext(cf, &config)
if err != nil {
log.Println("got error in creating gox http context config", err)
return
}
// Make a http call and get the result
// ResponseBuilder - this is used to convert json response to your custom object
//
// The following interface can be implemented to convert from bytes to the desired output.
// response.Response will hold the object which is returned from ResponseBuilder
//
// type ResponseBuilder interface {
// Response(data []byte) (interface{}, error)
// }
request := command.NewGoxRequestBuilder("getPosts").
WithContentTypeJson().
WithPathParam("id", 1).
WithResponseBuilder(command.NewJsonToObjectResponseBuilder(&gox.StringObjectMap{})).
Build()
response, err := goxHttpCtx.Execute(context.Background(), "getPosts", request)
if err != nil {
// Error details can be extracted from *command.GoxHttpError
if goxError, ok := err.(*command.GoxHttpError); ok {
if goxError.Is5xx() {
fmt.Println("got 5xx error")
} else if goxError.Is4xx() {
fmt.Println("got 5xx error")
} else if goxError.IsBadRequest() {
fmt.Println("got bad request error")
} else if goxError.IsHystrixCircuitOpenError() {
fmt.Println("hystrix circuit is open due to many errors")
} else if goxError.IsHystrixTimeoutError() {
fmt.Println("hystrix timeout because http call took longer then configured")
} else if goxError.IsHystrixRejectedError() {
fmt.Println("hystrix rejected the request because too many concurrent request are made")
} else if goxError.IsHystrixError() {
fmt.Println("hystrix error - timeout/circuit open/rejected")
}
} else {
fmt.Println("got unknown error")
}
} else {
fmt.Println(serialization.Stringify(response.Response))
// {some json response ...}
}
}
Retry Handling
You can specify following properties in a API to enable a retry.
- retry_count - how many times you want to retry
- retry_initial_wait_time_ms - a delay before making a retry
- NOTE - the total Hystrix timeout will be set to (timeout + (retry_count * timeout) + retry_initial_wait_time_ms)
Timeout is the time taken by a single call. So the total time is adjusted to cover retries - If response from a server is an acceptable code then retry will not be done e.g. in this case status=404 will not trigger a retry.
apis:
getPosts:
method: GET
path: /posts/{id}
server: jsonplaceholder
timeout: 1000
acceptable_codes: 200,201,404
retry_count: 3
retry_initial_wait_time_ms: 10
Environment Specific Configs Support
You can setup all properties with env specific values
- env = name of the env (default=prod). This is used to find the values for all properties
- add "env: " in front of all values to make it configurable
- setup env specific configs
- Note - You must use serialization.ReadParameterizedYaml() method if you uses parameterized yaml
host: "env:string: prod=localhost.prod; dev=localhost.dev; stage=localhost.stage"
Here host value will be based on the "env" you have provided in a config. For example host will be
"localhost.prod" if env=prod, or host="localhost.stage" if env=stage"
4. Default: You can sprcify "default" - if no value match this will be used
e.g. port: "env:int: prod=443; default=8080"
dev/stage/any other will pick port=8080. Only prod will use 443
env: dev
servers:
jsonplaceholder:
host: "env:string: prod=jsonplaceholder.typicode.com; stage=localhost.stage; default=localhost.dev"
port: "env:int: prod=443; default=8080"
https: true
connect_timeout: "env:int: prod=10; default=1000"
connection_request_timeout: "env:int: prod=11; default=1001"
testServer:
host: "env:string: prod=localhost.prod; dev=localhost.dev; stage=localhost.stage"
port: 9123
https: "env:int: prod=true; dev=false; stage=false"
apis:
delay_timeout_10:
path: /delay/delay_timeout_10
server: testServer
timeout: "env:int: prod=10; default=1000"
concurrency: "env:int: prod=10; default=300"
delay_timeout_10_POST:
path: /delay/delay_timeout_10_POST
method: POST
server: testServer
timeout: "env:int: prod=100; default=1001"
concurrency: "env:int: prod=11; default=200"
How to add or update a new API dynamically
NOTE - we only support adding new API (new server has not be done manually)
// Load config from test_config_real_server.yaml example file
config := command.Config{}
err := serialization.ReadYamlFromString(testhelper.TestConfigWithRealServer, &config)
// Suppose a API "delay_timeout_10" was using a path "/delay", and you want to chnage it to point to "/delay_new"
// Update the config and reload API
config.Apis["delay_timeout_10"].Path = "/delay_new"
err = goxHttpCtx.ReloadApi("delay_timeout_10")
// Add a new API inimically
config.Apis["new_api"] = &command.Api{
Name: "new_api",
Method: "GET",
Path: "/bad_new",
Server: "testServer",
Timeout: 100,
Concurrency: 10,
QueueSize: 10,
}
err = goxHttpCtx.ReloadApi("new_api")
request = command.NewGoxRequestBuilder("new_api").
WithContentTypeJson().
WithPathParam("id", 1).
WithResponseBuilder(command.NewJsonToObjectResponseBuilder(&gox.StringObjectMap{})).
Build()
response, err = goxHttpCtx.Execute(ctx, "new_api", request)
assert.NoError(t, err)
assert.Equal(t, "ok", response.AsStringObjectMapOrEmpty().StringOrEmpty("status"))
assert.Equal(t, "/bad_new", response.AsStringObjectMapOrEmpty().StringOrEmpty("url"))
Enable HmacSha256 validation
- interceptor_config.hmac_config => set this up to use HMAC SHA256
- key => secret key to use for HMAC
- hash_header_key => Hash will be calculated and will be passed with this header key
- timestamp_header_key => timestamp will be passed with this header key
- headers_to_include_in_signature => list of headers which will be included in signature calculation
- convert_header_keys_to_lower_case => if true then all header keys will be converted to lower case before calculating signature
servers:
jsonplaceholder:
host: jsonplaceholder.typicode.com
port: 443
https: true
interceptor_config:
hmac_config:
key: <some secret>
hash_header_key: X-Hash-code-sha
timestamp_header_key: X-Time
headers_to_include_in_signature: [x-header-1, x-header-2]
convert_header_keys_to_lower_case: true
# Packages
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author