Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better unwrapping of java exception #74

Merged
merged 10 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,89 @@ service EchoService {
}
```

### 异常处理

**codec-dubbo** 将异常定义为实现了以下接口的错误,你可以像处理错误一样处理 java 中的异常:
```go
type Throwabler interface {
Error() string
JavaClassName() string
GetStackTrace() []StackTraceElement
}
```

#### 常见异常

**codec-dubbo** 在[pkg/hessian2/exception](https://github.com/kitex-contrib/codec-dubbo/tree/main/pkg/hessian2/exception)目录下提供了java中常见的异常,目前支持 java.lang.Exception ,更多异常将在后续迭代中加入。
常见异常无需命令行工具的支持,直接引用即可。

##### client端提取异常

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func main() {
resp, err := cli.Greet(context.Background(), true)
if err != nil {
// FromError 返回 Throwabler
exceptionRaw, ok := hessian2_exception.FromError(err)
if !ok {
// 视作常规错误处理
} else {
// 若不关心 exceptionRaw 的具体类型,直接调用 Throwabler 提供的方法即可
klog.Errorf("get %s type Exception", exceptionRaw.JavaClassName())

// 若想获得 exceptionRaw 的具体类型,需要进行类型转换,但前提是已知该具体类型
exception := exceptionRaw.(*hessian2_exception.Exception)
}
}
}
```

##### server端返回异常

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func (s *GreetServiceImpl) Greet(ctx context.Context, req string) (resp string, err error) {
return "", hessian2_exception.NewException("Your detailed message")
}
```

#### 自定义异常

java 中的自定义业务异常往往会继承一个基础异常,这里以 CustomizedException 为例,CustomizedException 继承了 java.lang.Exception:
```java
public class CustomizedException extends Exception {
private final String customizedMessage;
public CustomizedException(String customizedMessage) {
super();
this.customizedMessage = customizedMessage;
}

public String getCustomizedMessage() {
return this.customizedMessage;
}
}
```

为了在 kitex 侧定义与之对应的异常,在 **thrift** 中编写如下定义:

```thrift
exception CustomizedException {
1: required java.Exception exception (thrift.nested="true")
2: required string customizedMessage
}(JavaClassName="org.cloudwego.kitex.samples.api.CustomizedException")
```

和[其它类型](#其它类型javalangobject-javautildate)一样,需要在使用 **kitex** 脚手架工具生成代码时添加 `-hessian2 java_extension` 参数来拉取拓展包。

使用方法与[常见异常](#常见异常)一致。

## 服务注册与发现

> 目前仅支持基于 zookeeper 的**接口级**服务发现与服务注册,**应用级**服务发现以及服务注册计划在后续迭代中支持。
Expand Down
84 changes: 84 additions & 0 deletions README_ENG.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,90 @@ service EchoService {
}
```

### Exception Handling

**codec-dubbo** defines exceptions as **error** that implement the following interface. You can handle exceptions in Java as you could handle **error** in Go:

```go
type Throwabler interface {
Error() string
JavaClassName() string
GetStackTrace() []StackTraceElement
}
```

#### Common Exceptions

**codec-dubbo** provides commonly used Java exceptions in the [pkg/hessian2/exception](https://github.com/kitex-contrib/codec-dubbo/tree/main/pkg/hessian2/exception) directory. Currently, it supports java.lang.Exception, and more exceptions will be added in subsequent iterations.
Common exceptions do not require command line tool support and could be directly referenced.

##### Extracting Exception on the Client Side

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func main() {
resp, err := cli.Greet(context.Background(), true)
if err != nil {
// FromError returns a Throwabler
exceptionRaw, ok := hessian2_exception.FromError(err)
if !ok {
// Treat as a regular error handling
} else {
// If you are not concerned with the specific type of exceptionRaw, just call the methods provided by Throwabler
klog.Errorf("get %s type Exception", exceptionRaw.JavaClassName())

// If you want to obtain the specific type of exceptionRaw, you need to perform a type conversion, but this requires knowing the specific type
exception := exceptionRaw.(*hessian2_exception.Exception)
}
}
}
```

##### Returning Exception on the Server Side

```go
import (
hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

func (s *GreetServiceImpl) Greet(ctx context.Context, req string) (resp string, err error) {
return "", hessian2_exception.NewException("Your detailed message")
}
```

#### Customized Exceptions

Customized business exceptions in Java often inherit a base exception. Here, we use CustomizedException as an example, which inherits from java.lang.Exception:

```java
public class CustomizedException extends Exception {
private final String customizedMessage;
public CustomizedException(String customizedMessage) {
super();
this.customizedMessage = customizedMessage;
}

public String getCustomizedMessage() {
return this.customizedMessage;
}
}
```

To define a corresponding exception on the kitex side, write the following definition in **thrift**:

```thrift
exception CustomizedException {
1: required java.Exception exception (thrift.nested="true")
2: required string customizedMessage
}(JavaClassName="org.cloudwego.kitex.samples.api.CustomizedException")
```

Like [other types](#other-types--javalangobject-javautildate-), you need to add the `-hessian2 java_extension` parameter when generating code with the **kitex** scaffolding tool to pull the extension package.

The usage is consistent with [Common Exceptions](#common-exceptions).

## Service Registry and Service Discovery

Expand Down
28 changes: 15 additions & 13 deletions java/java-reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,26 @@ import (
// IDL Path: java.thrift

var file_java_thrift_go_types = []interface{}{
(*Object)(nil), // Struct 0: java.Object
(*Date)(nil), // Struct 1: java.Date
(*Object)(nil), // Struct 0: java.Object
(*Date)(nil), // Struct 1: java.Date
(*Exception)(nil), // Struct 2: java.Exception
}

var (
file_java_thrift *thrift_reflection.FileDescriptor
file_idl_java_rawDesc = []byte{
0x1f, 0x8b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x8c, 0x8f, 0x4d, 0xe, 0x82, 0x30,
0x10, 0x85, 0x9f, 0xa5, 0xe0, 0xcf, 0x50, 0xeb, 0x49, 0x7a, 0x9, 0x5d, 0xb9, 0xd0, 0x33, 0x8c,
0xa6, 0x22, 0xa4, 0x6a, 0x22, 0x95, 0xf3, 0x9b, 0x29, 0x65, 0x65, 0x4c, 0x58, 0x7d, 0x4d, 0xfa,
0xde, 0xf7, 0x32, 0x84, 0x5, 0x0, 0xea, 0x78, 0x60, 0x17, 0xef, 0xef, 0xf6, 0x16, 0xd, 0x14,
0x11, 0x0, 0x18, 0x14, 0xe9, 0x21, 0x1, 0xd5, 0xbc, 0x0, 0x68, 0x89, 0x59, 0xe8, 0x5a, 0xbe,
0x2d, 0x4a, 0xa1, 0xfa, 0x35, 0x10, 0x14, 0x80, 0xea, 0x7c, 0xe9, 0xfc, 0x35, 0x5a, 0x14, 0xf5,
0x68, 0xd3, 0x64, 0xb3, 0xcd, 0x1c, 0x79, 0xe0, 0x7d, 0xe0, 0xbe, 0x3f, 0xf1, 0xc3, 0x4f, 0x1b,
0xbb, 0xa4, 0x8, 0xfc, 0x6c, 0xdc, 0x58, 0x25, 0x94, 0xd2, 0xc4, 0xbf, 0x5, 0x7d, 0xe0, 0xe8,
0xe7, 0xfb, 0xb7, 0x49, 0xf0, 0x89, 0x6d, 0x70, 0x52, 0x9c, 0xec, 0x16, 0x55, 0xbe, 0x67, 0x99,
0xb9, 0xca, 0x5c, 0x67, 0x6e, 0x12, 0xf1, 0xd, 0x0, 0x0, 0xff, 0xff, 0xaf, 0x1, 0x47, 0x6a,
0x2b, 0x1, 0x0, 0x0,
0x1f, 0x8b, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x8c, 0x90, 0x41, 0xae, 0x82, 0x30,
0x18, 0x84, 0xe7, 0x41, 0xe1, 0x89, 0x3f, 0x58, 0x13, 0xef, 0xc1, 0x25, 0xd4, 0x8d, 0xb, 0x3d,
0xc3, 0x2f, 0xa9, 0x8, 0xa9, 0x60, 0xa4, 0x12, 0x8f, 0x6f, 0x5a, 0x8a, 0x2e, 0x8c, 0x91, 0xd5,
0xd7, 0xa4, 0x33, 0xdf, 0xa4, 0x25, 0xfc, 0x1, 0xa0, 0x9a, 0x7b, 0xce, 0xcd, 0xf9, 0x56, 0x9d,
0x4c, 0x86, 0x80, 0x8, 0x0, 0x32, 0x84, 0xee, 0x60, 0x3, 0x41, 0xd9, 0x2, 0x10, 0x36, 0x26,
0x21, 0x52, 0x7b, 0x2d, 0x11, 0x59, 0x86, 0x9f, 0x6, 0x42, 0x0, 0x20, 0x3e, 0x1c, 0x6b, 0x55,
0x18, 0x89, 0x30, 0x1d, 0x6c, 0x82, 0xa4, 0xb7, 0x65, 0x3b, 0xee, 0x79, 0xad, 0xb9, 0xeb, 0xf6,
0x7c, 0x51, 0xe3, 0xc6, 0xd2, 0x29, 0x34, 0x37, 0x65, 0x3e, 0x54, 0x9, 0x91, 0x6d, 0xe2, 0xdb,
0x82, 0xd8, 0xb0, 0x51, 0xd3, 0xfd, 0xb, 0x27, 0xb8, 0x9b, 0x4a, 0xe7, 0xb6, 0xf8, 0xcb, 0x9e,
0x6c, 0x1f, 0x85, 0xba, 0x9a, 0xaa, 0x6d, 0xa6, 0x4f, 0xac, 0xde, 0x4f, 0x78, 0xb5, 0xc7, 0x1d,
0x89, 0xd8, 0xff, 0xdb, 0xbf, 0xe7, 0xcc, 0x33, 0xf1, 0x9c, 0x3b, 0xe2, 0x19, 0x0, 0x0, 0xff,
0xff, 0xf9, 0xde, 0xba, 0xfa, 0x93, 0x1, 0x0, 0x0,
}
)

Expand Down
8 changes: 8 additions & 0 deletions java/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package java

import (
"time"

hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"
)

type Object = interface{}
Expand All @@ -34,3 +36,9 @@ type Date = time.Time
func NewDate() *Date {
return new(Date)
}

type Exception = hessian2_exception.Exception

func NewException(detailMessage string) *Exception {
return hessian2_exception.NewException(detailMessage)
}
2 changes: 2 additions & 0 deletions java/java.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ namespace go java
struct Object {} (JavaClassName="java.lang.Object")

struct Date {} (JavaClassName="java.util.Date")

struct Exception {} (JavaClassName="java.lang.Exception")
7 changes: 5 additions & 2 deletions pkg/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"context"
"fmt"

hessian2_exception "github.com/kitex-contrib/codec-dubbo/pkg/hessian2/exception"

"github.com/kitex-contrib/codec-dubbo/registries"

"github.com/cloudwego/kitex/pkg/remote"
Expand Down Expand Up @@ -182,12 +184,13 @@ func (m *DubboCodec) encodeExceptionPayload(ctx context.Context, message remote.
if !ok {
return nil, fmt.Errorf("%v exception does not implement Error", data)
}
if exception, ok := data.(hessian2.Throwabler); ok {
// exception is wrapped by kerrors.DetailedError
if exception, ok := hessian2_exception.FromError(errRaw); ok {
if err := encoder.Encode(exception); err != nil {
return nil, err
}
} else {
if err := encoder.Encode(hessian2.NewException(errRaw.Error())); err != nil {
if err := encoder.Encode(hessian2_exception.NewException(errRaw.Error())); err != nil {
return nil, err
}
}
Expand Down
30 changes: 0 additions & 30 deletions pkg/hessian2/exception.go

This file was deleted.

59 changes: 59 additions & 0 deletions pkg/hessian2/exception/exception.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2023 CloudWeGo Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package exception

import (
"github.com/apache/dubbo-go-hessian2/java_exception"
)

type Throwabler = java_exception.Throwabler

type Exception = java_exception.Exception

func NewException(detailMessage string) *Exception {
return java_exception.NewException(detailMessage)
}

// FromError extracts Throwabler from passed err.
//
// - If err is nil, it returns nil and false
//
// - If err implements Unwrap(), it would unwrap err until getting the real cause.
// Then it would check cause whether implementing Throwabler. If yes, it returns
// Throwabler and true.
//
// If not, it checks err whether implementing Throwabler directly. If yes,
// it returns Throwabler and true.
func FromError(err error) (Throwabler, bool) {
if err == nil {
return nil, false
}
for {
if wrapper, ok := err.(interface{ Unwrap() error }); ok {
err = wrapper.Unwrap()
} else {
break
}
}
if exception, ok := err.(Throwabler); ok {
return exception, true
}
return nil, false
}
Loading
Loading