背景
我将JSON数据从HTTP API删除到以下Golang结构:
type ResponseBody struct {
Version string `json:"jsonrpc"`
Result Result `json:"result"`
Error Error `json:"error"`
Id int `json:"id"`
}
type Result struct {
Random struct {
Data interface{} `json:"data"`
CompletionTime string `json:"completionTime"`
} `json:"random"`
BitsUsed int `json:"bitsUsed"`
BitsLeft int `json:"bitsLeft"`
RequestsLeft int `json:"requestsLeft"`
AdvisoryDelay int `json:"advisoryDelay"`
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data []int `json:"data,omitempty"`
}
我已经实现了Error
的错误接口,如下所示:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e)
}
到目前为止相关代码:
func Request(method string, params interface{}) (Result, error) {
// `error` in return types is _not_ a typo
body, err := json.Marshal(RequestBody{Version: "2.0", Params: params, Method: method, Id: 1})
if err != nil {
return Result{}, err
}
resp, err := http.Post(endpoint, "application/json-rpc", bytes.NewReader(body))
if err != nil {
return Result{}, fmt.Errorf("Request failed, error was %s", err)
}
defer resp.Body.Close()
text, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Result{}, fmt.Errorf("Failed to read response into memory, error was: %s", err)
}
if resp.StatusCode != 200 {
var errorMessage Error
if err := json.Unmarshal(text, &errorMessage); err != nil {
return Result{}, Error{
Code: 409,
Message: fmt.Sprintf("Client could not decode JSON error response, received %s, error was: %s", text, err),
Data: []int{},
}
}
return Result{}, errorMessage
}
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
return response.Result, response.Error
}
问题
以下代码无限期地悬挂在成功的呼叫中而不会感到恐慌:
// `body` here is just any old struct
result, err := Request("generateIntegers", body)
if err != nil {
return []int{}, err // hangs here
}
实际上,当我调用err
时,代码总是悬挂。没有恐慌会引起且没有返回错误 - 它只是冷冻[1]。
[1]严格地说,它会导致堆栈溢出错误,但这是因为该功能永远不会返回,因此Request
中的递延resp.Body.Close()
从未被调用。
它变得奇怪。添加以下调试线:
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result.BitsUsed)
fmt.Println(response.Result) // this prints!
return response.Result, response.Error
有效,但将这些行更改为
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result) // this no longer prints! :O
return response.Result, response.Error
使Request
功能本身悬挂在此调试语句中。
我尝试过的东西
- 使用
go test -trace
运行跟踪以查看所谓的内容。由于go tool trace
无法解析生成的跟踪文件。 - 将我的签名转换为返回
*error
而不是常规error
。这没有帮助。
为什么此代码段悬挂?
注意:我正在运行1.7
问题是Error
接口的定义。当您尝试fmt.Sprintf
值e
时,Error
上的函数Error() string
正在递归地调用自己:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e) // calls e.Error again
}
尝试通过明确访问Error
的构件来返回错误:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e.Message)
}
问题是递归,因为fmt
调用Error()
再次称为FMT。
经典解决方案是将您的错误类型转换为string
,然后使用标准力学。这样,您就拥有所有格式化工具。喜欢:
type E string
func (e E) Error() string {
return fmt.Sprintf(string(e))
}
游乐场:https://play.golang.org/p/ni5jl3h4g7y
摘自fmt
文档(不幸的是无法直接链接到段落):
如果操作数实现了错误接口,则将调用错误方法将对象转换为字符串,然后将其按照动词的要求(如果有)进行格式。
如果操作数实现方法字符串()字符串,则将调用该方法将对象转换为字符串,然后根据动词的要求(如果有)将其格式化。
在诸如
的情况下避免递归type X string func (x X) String() string { return Sprintf("<%s>", x) }
重复之前转换值:
func (x X) String() string { return Sprintf("<%s>", string(x)) }
无限递归也可以通过自指数据触发结构,例如包含自己作为元素的切片,如果该类型具有字符串方法。但是,这种病理很少包装不能防止它们。
打印结构时,FMT不能,因此不会调用格式化方法,例如错误字段上的错误或字符串。