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

First-class HTTP header support #23

Merged
merged 5 commits into from
Mar 8, 2018
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
2 changes: 1 addition & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

These folks have contributed code and time to grpc-jersey.

- xorlev
- xorlev (Google LLC)
- sypticus
- gfecher
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2017 Michael Rose and contributors.
Copyright 2017 Michael Rose, Google LLC, and contributors.


Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
94 changes: 92 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ used by the [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) proje
* [Example Usage](#example-usage)
* [Operation modes](#operation-modes)
* [HTTP and gRPC](#http-and-grpc)
* [Working with HTTP headers](#working-with-http-headers)
* [Streaming RPCs](#streaming-rpcs)
* [Error handling](#error-handling)
* [Error Translation](#error-translation)
Expand Down Expand Up @@ -264,6 +265,87 @@ data: {"request":{"s":"hello","uint3":0,"uint6":"0","int3":2,"int6":"0","bytearr
data: {"request":{"s":"hello","uint3":0,"uint6":"0","int3":2,"int6":"0","bytearray":"","boolean":false,"f":0.0,"d":0.0,"enu":"FIRST","rep":[],"repStr":[]}}
```

## Working with HTTP headers

_NOTE:_ This only works for uses using the "proxy" configuration. Direct invocation mode does not support HTTP header
manipulation.

grpc-jersey allows you to use and manipulate HTTP headers from within your RPC handler. To do so, you'll need to install
a server interceptor into your RPC stack. If you're using the recommended "dual-stack" configuration, you can modify it
like so:

```java
// Service stack. This is where you define your interceptors.
ServerServiceDefinition serviceStack = ServerInterceptors.intercept(
GrpcJerseyPlatformInterceptors.intercept(new EchoTestService()),
new GrpcLoggingInterceptor(),
new MyAuthenticationInterceptor() // your interceptor stack.
);
```

Preferably, you'll want to use the helper provided by `GrpcJerseyPlatformInterceptors`. Future platform interceptors
will be added here automatically, allowing your code to remain the same and take advantage of new functionality.
However, you can also use just the HttpHeaderInterceptor directly should you desire:

```java
// Service stack. This is where you define your interceptors.
ServerServiceDefinition serviceStack = ServerInterceptors.intercept(
new EchoTestService(),
HttpHeaderInterceptors.serverInterceptor(),
new GrpcLoggingInterceptor(),
new MyAuthenticationInterceptor() // your interceptor stack.
);
```

The client interceptor is automatically attached to your stubs by code generation after grpc-jersey 0.3.0.

To read & manipulate HTTP headers, below is an example right from the `EchoTestService` in this project:

```java
@Override
public void testMethod3(TestRequest request, StreamObserver<TestResponse> responseObserver) {
for (Map.Entry<String, String> header : HttpHeaderContext.requestHeaders().entries()) {
if (header.getKey().startsWith("grpc-jersey")) {
HttpHeaderContext.setResponseHeader(header.getKey(), header.getValue());
}
}

responseObserver.onNext(TestResponse.newBuilder().setRequest(request).build());
responseObserver.onCompleted();
}
```

`HttpHeaderContext` is your interface into the HTTP headers. You can see request headers with
`HttpHeaderContext.requestHeaders()` and set response headers with
`HttpHeaderContext.setResponseHeader(headerName, headerValue)` or
`HttpHeaderContext.addResponseHeader(headerName, headerValue)`, the former setting a single value (or list of values),
clearing existing ones, and the latter adding values. You can use `HttpHeaderContext.clearResponseHeader(headerName)`
or `HttpHeaderContext.clearResponseHeaders()` to remove header state. **Note:** this API is considered beta and may
change in the future.

While `HttpHeaderContext` is gRPC Context-aware and request headers can be safely accessed from background threads
executed with an attached context, manipulating response headers should only be done from a single thread as no effort
is put into synchronizing the state.

### Streaming

Streaming RPCs will apply any headers set before the first message is sent or before the stream is closed if no messages
are sent.

### Errors

Headers are optionally applied by the GrpcJerseyErrorHandler on error (for unary RPCs). The default (provided)
implementation will honor headers set by the RPC handler on all error responses.

Additionally, as per the caveats with streaming RPCs in general, any additional headers added to an in-progress
stream will be ignored, as headers can only be sent once in HTTP/1.x's common implementations, only headers present
before the first message (or close/error) will be applied.

### Headers in the main gRPC Metadata (deprecated)

HTTP request headers are read into the main gRPC Metadata when using the "proxy" mode by default, however this is
considered deprecated behavior. Utilizing the new `HttpHeaderContext` is the supported method.

## Error handling

grpc-jersey will translate errors raised inside your RPC handler. However, there is some nuance with regards to using
Expand Down Expand Up @@ -402,6 +484,13 @@ instances.

## Releases

0.3.0
- First-class HTTP header support. HTTP request headers are read into and attached to the gRPC Context. Likewise,
response headers can be controlled from within your RPC handler. See
[Working with HTTP headers](#working-with-http-headers). [#23](https://github.com/Xorlev/grpc-jersey/pull/23)
- **Breaking change:** API of GrpcJerseyErrorHandler has changed. If you haven't implemented a custom error handler,
this doesn't affect you. If so, please migrate your handler to the new API.

0.2.0
- Server-to-client RPC streaming support. [#14](https://github.com/Xorlev/grpc-jersey/pull/14)
- `ALREADY_EXISTS` gRPC error code now maps to `409 Conflict`.
Expand Down Expand Up @@ -443,8 +532,9 @@ Short-term roadmap:
- [X] Server streaming
- [ ] Client streaming
- [ ] BiDi streaming (true bidi streaming is impossible without websockets)
- [ ] Direct control of HTTP headers
- [ ] Deadline handling
- [x] Direct control of HTTP headers
- [ ] Out of the box CORS support
- [ ] Better deadline handling

Long-term roadmap:
- Potentially replace Jersey resources with servlet filter. This would make streaming easier.
Expand Down
16 changes: 7 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ plugins {

ext {
grpcVersion = "1.8.0"
jerseyVersion = "2.25.1"
lombokVersion = "1.16.18"
protobufVersion = "3.5.0"
}
Expand Down Expand Up @@ -92,8 +93,9 @@ project(":jersey-rpc-support") {
compile "com.google.protobuf:protobuf-java-util:${protobufVersion}"
compile "io.grpc:grpc-protobuf:${grpcVersion}"
compile "io.grpc:grpc-stub:${grpcVersion}"
compile "javax.servlet:javax.servlet-api:3.1.0"
compile "javax.ws.rs:javax.ws.rs-api:2.0.1"
provided "org.glassfish.jersey.core:jersey-server:2.25"
provided "org.glassfish.jersey.core:jersey-server:${jerseyVersion}"
}

protobuf {
Expand Down Expand Up @@ -137,7 +139,7 @@ project(":protoc-gen-jersey") {
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.8.5"
compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"

testCompile 'org.glassfish.jersey.core:jersey-common:2.24.1'
testCompile "org.glassfish.jersey.core:jersey-common:${jerseyVersion}"
}

protobuf {
Expand Down Expand Up @@ -263,9 +265,7 @@ project(":integration-test-base") {
testCompile('io.dropwizard:dropwizard-testing:1.0.5') {
exclude group: 'org.eclipse.jetty'
}
testCompile 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-jetty:2.23.2'


testCompile "org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:${jerseyVersion}"
}

protobuf {
Expand Down Expand Up @@ -310,7 +310,7 @@ project(":integration-test-serverstub") {
exclude group: 'org.eclipse.jetty'
}
testCompile group: 'io.dropwizard', name: 'dropwizard-jetty', version: '0.9.3'
testCompile 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-jetty:2.25.1'
testCompile "org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:${jerseyVersion}"
}

protobuf {
Expand Down Expand Up @@ -366,9 +366,7 @@ project(":integration-test-proxy") {
exclude group: 'org.eclipse.jetty'
}
testCompile group: 'io.dropwizard', name: 'dropwizard-jetty', version: '0.9.3'
testCompile 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-jetty:2.25.1'


testCompile "org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:${jerseyVersion}"
}

protobuf {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.2.1-SNAPSHOT
version=0.3.0-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import com.fullcontact.rpc.TestRequest;
import com.fullcontact.rpc.TestResponse;
import com.fullcontact.rpc.TestServiceGrpc;
import com.google.common.collect.ImmutableMultimap;
import com.google.protobuf.util.Durations;
import com.google.rpc.DebugInfo;
import com.google.rpc.RetryInfo;
import io.grpc.Context;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;

import java.util.Map;

/**
* gRPC service that echos the request into the response
*
Expand All @@ -30,6 +34,12 @@ public void testMethod2(TestRequest request, StreamObserver<TestResponse> respon

@Override
public void testMethod3(TestRequest request, StreamObserver<TestResponse> responseObserver) {
for (Map.Entry<String, String> header : HttpHeaderContext.requestHeaders().entries()) {
if (header.getKey().startsWith("grpc-jersey")) {
HttpHeaderContext.setResponseHeader(header.getKey(), header.getValue());
}
}

responseObserver.onNext(TestResponse.newBuilder().setRequest(request).build());
responseObserver.onCompleted();
}
Expand All @@ -54,6 +64,8 @@ public void testMethod6(TestRequest request, StreamObserver<TestResponse> respon

@Override
public void streamMethod1(TestRequest request, StreamObserver<TestResponse> responseObserver) {
HttpHeaderContext.addResponseHeader("X-Stream-Test", "Hello, World!");

for(int i = 0; i < request.getInt3(); i++) {
responseObserver.onNext(TestResponse.newBuilder().setRequest(request).build());

Expand Down
Loading