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

Unframed grpc service support richer error model #4231

Merged
merged 39 commits into from
Aug 4, 2022

Conversation

natsumehu
Copy link
Contributor

@natsumehu natsumehu commented Apr 26, 2022

Motivation:

We want to add another Unframed Grpcerrorhandler to support richer grpc error information
google errors

Modifications:

  • add ofRichJson() method to UnframedGrpcErrorHandler.

Result:

eg.

  • if you throw exceptions like
final ErrorInfo errorInfo = ErrorInfo.newBuilder()
                                    .setDomain("test")
                                    .setReason("Unknown Exception").build();
final com.google.rpc.Status status =
       com.google.rpc.Status.newBuilder()
                            .setCode(Code.UNKNOWN.getNumber())
                            .setMessage("Unknown Exceptions Test")
                            .addDetails(Any.pack(errorInfo))
                            .build();

responseObserver.onError(StatusProto.toStatusRuntimeException(status));

You can use this error handler by doing

sb.service(GrpcService.builder()   
                      .addService(grpcService)   
                      .unframedGrpcErrorHandler(UnframedGrpcErrorHandler.ofRichJson())   
                      .build());

You can get a response

{
    "code": 2,
    "message": "Unknown Exceptions Test",
    "details": [{
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "Unknown Exception",
        "domain": "test"
    }]
}

@ikhoon
Copy link
Contributor

ikhoon commented Apr 26, 2022

#4204 has been merged. 😉

@codecov
Copy link

codecov bot commented Apr 26, 2022

Codecov Report

Merging #4231 (21ce155) into master (dd31525) will increase coverage by 0.00%.
The diff coverage is 84.73%.

@@            Coverage Diff            @@
##             master    #4231   +/-   ##
=========================================
  Coverage     73.51%   73.52%           
- Complexity    17663    17677   +14     
=========================================
  Files          1504     1505    +1     
  Lines         66100    66172   +72     
  Branches       8338     8346    +8     
=========================================
+ Hits          48594    48652   +58     
- Misses        13292    13297    +5     
- Partials       4214     4223    +9     
Impacted Files Coverage Δ
.../armeria/server/grpc/UnframedGrpcErrorHandler.java 75.00% <75.00%> (-14.84%) ⬇️
...armeria/server/grpc/UnframedGrpcErrorHandlers.java 85.36% <85.36%> (ø)
...ecorp/armeria/server/LengthBasedServiceNaming.java 75.00% <0.00%> (-16.67%) ⬇️
...eria/internal/common/stream/StreamMessageUtil.java 61.11% <0.00%> (-5.56%) ⬇️
...necorp/armeria/common/util/UnmodifiableFuture.java 94.23% <0.00%> (-3.85%) ⬇️
...inecorp/armeria/server/file/StreamingHttpFile.java 54.78% <0.00%> (-2.61%) ⬇️
...ecorp/armeria/server/grpc/StreamingServerCall.java 81.11% <0.00%> (-2.23%) ⬇️
...p/armeria/common/stream/FuseableStreamMessage.java 81.36% <0.00%> (-1.87%) ⬇️
...rmeria/common/stream/DefaultByteStreamMessage.java 63.02% <0.00%> (-1.69%) ⬇️
...a/internal/common/stream/ByteBufsDecoderInput.java 84.89% <0.00%> (-1.44%) ⬇️
... and 15 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@natsumehu natsumehu changed the title Unframed grpc service support richer error model. [WIP] Unframed grpc service support richer error model Apr 27, 2022
@natsumehu natsumehu marked this pull request as ready for review April 27, 2022 04:13
@natsumehu natsumehu changed the title [WIP] Unframed grpc service support richer error model Unframed grpc service support richer error model Apr 27, 2022
@ikhoon ikhoon added this to the 1.17.0 milestone Apr 27, 2022
@ikhoon
Copy link
Contributor

ikhoon commented May 13, 2022

Gentle ping. @natsumehu

@natsumehu
Copy link
Contributor Author

Gentle ping. @natsumehu

Will continue working on this feature these two days 🎉

@natsumehu natsumehu requested a review from ikhoon May 16, 2022 13:49
@natsumehu
Copy link
Contributor Author

Gentle ping. @natsumehu

@ikhoon I think it's ready for another round.

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @natsumehu. I left super minor comments only. 👍

grpcCode.value())
.build();
final HttpData content;
try (TemporaryThreadLocals ttl = TemporaryThreadLocals.acquire()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that you already know this trick! 👍

@natsumehu natsumehu requested a review from jrhee17 June 22, 2022 11:58
@natsumehu natsumehu requested review from minwoox, ikhoon and trustin June 23, 2022 05:50
@minwoox minwoox modified the milestones: 1.17.0, 1.18.0 Jun 27, 2022
@natsumehu natsumehu requested a review from ikhoon July 25, 2022 11:32
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @natsumehu! 🚀❤️

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall! I think this will be my last comments 🙏

try (OutputStream outputStream = new ByteBufOutputStream(buffer);
JsonGenerator jsonGenerator = mapper.createGenerator(outputStream)) {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("error");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, one last question.
Should we just return a Status object instead of wrapping in an error object?

i.e.

{
    "code": ..
    "message":..
    "stack-trace":...
    "details":...
}

instead of

{
    "error": {
        "code": ..
        "message":..
        "stack-trace":...
        "details":...
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, wrap the status object in an error object is what google's error model does. ❤️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 😅 I missed this the last time I reviewed, but other open source implementations don't seem to be wrapping in an error object.

#4231 (comment)

Copy link
Contributor Author

@natsumehu natsumehu Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. That's a decision we should make. I guess the purpose of wrapping an error object is making the error handling more extensive and I probably prefer this option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable 👍

Copy link
Contributor Author

@natsumehu natsumehu Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also for client developers, when the error is wrapped in a single error object, it seems to be easier and more intuitive to detect the error message instead of having a flat normal response.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google's API Platform uses error object for backward compatibiltiy.
https://cloud.google.com/apis/design/errors#http_mapping

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;

final class DefaultUnframedGrpcErrorHandler {
Copy link
Contributor

@minwoox minwoox Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming to UnframedGrpcErrorHandlerUtil?
because this isn't the default class of UnframedGrpcErrorHandler but just a util class that has a bunch of static methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about UnframedGrpcErrorHandlers?

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks @natsumehu ! 🙇 👍 🚀

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, @natsumehu!
gRPC users would love this feature.
Thanks, @ikhoon for taking care of the comments. 😉

@minwoox minwoox merged commit 7617ac4 into line:master Aug 4, 2022
heowc pushed a commit to heowc/armeria that referenced this pull request Sep 24, 2022
Motivation:

We want to add another Unframed Grpcerrorhandler to support richer grpc error information
[google errors](https://grpc.io/docs/guides/error/)

Modifications:

- add `ofRichJson()` method to `UnframedGrpcErrorHandler`.

Result:

- You can have a richer error model as described by [google error model](https://cloud.google.com/apis/design/errors#error_model) 

eg.  
- if you throw exceptions like 

 ```java
final ErrorInfo errorInfo = ErrorInfo.newBuilder()
                                     .setDomain("test")
                                     .setReason("Unknown Exception").build();
final com.google.rpc.Status status =
        com.google.rpc.Status.newBuilder()
                             .setCode(Code.UNKNOWN.getNumber())
                             .setMessage("Unknown Exceptions Test")
                             .addDetails(Any.pack(errorInfo))
                             .build();

responseObserver.onError(StatusProto.toStatusRuntimeException(status));
```
You can use this error handler by doing
``` java
sb.service(GrpcService.builder()   
                      .addService(grpcService)   
                      .unframedGrpcErrorHandler(UnframedGrpcErrorHandler.ofRichJson())   
                      .build());
```

You can get a response 

```
{
	"error": {
		"code": 500,
		"status": "UNKNOWN",
		"message": "Unknown Exceptions Test",
		"details": [{
			"@type": "type.googleapis.com/google.rpc.ErrorInfo",
			"reason": "Unknown Exception",
			"domain": "test"
		}]
	}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants