有两个go应用程序,一个是 stapi 在端口8050上侦听并提供RESTful API,另一个是客户端来使用这些API。
两者都在不同的服务器上运行,客户端在使用HTTP POST方法调用API时经常出错。以下是来自客户端日志的几行(真实IP被假想的替换)
2018/02/17 11:42:58 ERROR: [DoLogin] API Error: [Post https://123.123.123.123:8050/v1/st/verifyuser: dial tcp 123.123.123.123:8050: getsockopt: connection refused]
2018/02/17 11:47:14 ERROR: [CreateAttempt] Error: [Post https://123.123.123.123:8050/v1/userattempts/createattempt: dial tcp 123.123.123.123:8050: getsockopt: connection refused]
它是间歇性的并且使应用程序不可靠,在大约1k请求中我得到了大约50+请求的错误。
最初 stapi 正在侦听所有IP
httpSrv := http.Server{
Addr: ":8050",
Handler: router, // < gin router
...
}
但在阅读了Golang HTTP Post error: connection refused中的workaroung之后,我修改了 stapi 应用程序并让它在不同的IP上进行侦听,如下所示
$ sudo lsof -i -P -n | grep LISTEN
stapi 4775 samtech 10u IPv4 2388179 0t0 TCP 123.123.123.123:8050 (LISTEN)
stapi 4775 samtech 11u IPv6 2388181 0t0 TCP [::1]:8050 (LISTEN)
stapi 4775 samtech 12u IPv4 2388183 0t0 TCP 127.0.0.1:8050 (LISTEN)
但问题仍然存在,我还应该检查和修复什么?请建议。
API受JWT保护,以下是客户端发出POST请求的方式
func (w *OST) DoLogin(c *gin.Context) {
...
ud := stapimodels.UserLogin{}
err := c.BindJSON(&ud)
...
//call api to save user response
url := config.AppConfig.APIBaseURL + "st/verifyuser"
res, err := api.JwtApi.APIPost(url, &ud)
if err != nil {
g.Logger.Errorm("DoLogin", "Error: %v", err)
t.Error("Error", err.Error())
return
}
...
}
//APIPost - call given apiurl with POST method and pass data
func (j *JwtAPI) APIPost(apiurl string, postdata interface{}) (*APIResult, error) {
if postdata == nil {
return nil, fmt.Errorf("postdata is nil")
}
jsondata, err := toJSON(postdata)
if err != nil {
return nil, err
}
resp, err := j.makeRequest(http.MethodPost, apiurl, jsondata)
if err != nil {
return nil, err
}
defer resp.Body.Close()
res := APIResult{}
json.NewDecoder(resp.Body).Decode(&res)
return &res, nil
}
//makeRequest makes http request for given url with given method
// also inject Authorization Header
func (j *JwtAPI) makeRequest(method, apiurl string, body io.Reader) (*http.Response, error) {
retry := 0
//Create []byte buffer from body - so it can be passed in further retries
var buf []byte
if body != nil {
buf, _ = ioutil.ReadAll(body)
}
r, err := http.NewRequest(method, apiurl, bytes.NewReader(buf))
if err != nil {
return nil, err
}
r.Header.Set("Authorization", "bearer "+j.token.AccessToken)
r.Header.Set("Content-Type", "application/json")
client := j.getClient()
resp, err := client.Do(r)
if err != nil {
return nil, err
}
return resp, nil
}
func (j *JwtAPI) getClient() *http.Client {
// default timeout (if not set by client)
timeoutInSec := 10
if j.Timeout.Seconds() > 0 {
// client sets timeout, so use it
timeoutInSec = int(j.Timeout.Seconds())
}
client := &http.Client{
Timeout: time.Second * time.Duration(timeoutInSec),
}
return client
}
答案 0 :(得分:0)
为了使您的代码更具弹性,您应该添加一些带有后退的重试,因此即使连接被拒绝,它仍然有效。
拒绝连接意味着未打开端口。中间是否有防火墙或代理?认证部分在这里不重要,因为它甚至没有达到这一点。
您可以检查的一些事项:
退避重试的套餐
至于实施后退,你可以试试这个包:
https://github.com/cenkalti/backoff
列出了如何使用它的示例,它正是您所需要的:
// An operation that may fail.
operation := func() error {
// do the request here and check the response code (or check the response body depending on your need) . e.g. above 500 should retry, above 400 and below 500, it should be a client side error and retrying might not help much
return nil // or an error
}
err := Retry(operation, NewExponentialBackOff())
if err != nil {
// Handle error.
return
}
// Operation is successful.