grpc-java CallCredentials without thisUsesUnstableApi? - grpc

I've got a gRPC server acting as a proxy. It forwards a few auth-related headers in a ServerInterceptor/ClientInterceptor pair. See below for most of the code.
My question is: the only way I've seen so far to set Metadata headers for outgoing gRPC calls is to make a subclass of CallCredentials. To do that, though, you need to confess your sins by implementing the thisUsesUnstableApi method.
Is there any stable alternative to making a subclass of CallCredentials?
The ServerInterceptor grabs the relevant headers out of Metadata and stuffs them in the Context:
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
final Metadata requestHeaders,
ServerCallHandler<ReqT, RespT> next) {
Context contextWithAuth = Context.current();
// Cutting out my utility classes that won't help those looking for an example.
for (...) {
contextWithAuth = contextWithAuth.withValue(Context.key(foo), requestHeaders.get(Metadata.Key.of(...)));
}
return Contexts.interceptCall(contextWithAuth, call, requestHeaders, next);
}
The ClientInterceptor grabs the headers out of the Context and stuffs them in the outgoing Metadata:
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return next.newCall(
method,
callOptions.withCallCredentials(
new CallCredentials() {
#Override
public void applyRequestMetadata(
RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
try {
Metadata headers = new Metadata();
for (...) {
headers.put(Metadata.Key.of(...), Context.key(...).get());
}
applier.apply(headers);
} catch (RuntimeException e) {
applier.fail(Status.UNAUTHENTICATED.withCause(e));
}
}
#Override
public void thisUsesUnstableApi() {
}
}));
}

Most header processing is handled with ClientInterceptors (and ServerInterceptors). For example, to add a header to a client:
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
#Override
public void start(Listener<RespT> responseListener, Metadata headers) {
headers.put(MY_HEADER_KEY, "someValue");
super.start(responseListener, headers);
}
}
}
CallCredentials is less powerful and more powerful than ClientInterceptors. It can only add request headers, but it is easy to make asynchronous and it has access to precise connection information (security, remote ip, etc). Easy asynchronous is very helpful when needing do I/O to obtain an OAuth token and being able to access security information of the transport is helpful to ensure that it is safe to transmit the bearer token. It is also called just before sending the RPC so the token can be very fresh. This doesn't sound all that relevant for you.
If you are wanting to use CallCredentials and just bothered by the unstable API then you can encourage it to be stabilized on its experimental-tracking issue. It is an older API and hasn't changed much, so may be close to stabilization.
(Aside: the annoying thisUsesUnstableApi() method is because the name CallCredentials is stable, so users can pass them around safely, but implementing it and calling it is unstable. That is a rare situation so the method is there to warn you.)
If you are making a proxy, it may be easier to use ClientCall and ServerCall directly, without the stubs. That carries the cost of needing to manage flow control directly (e.g., calling request()), but overall might be a better fit. You also may be interested in my fully-generic proxy example.

Related

How to configure client-side load balancing with grpc-java

I've seen some high-level information about load balancing, but am struggling to put the pieces together. Here's what I've reviewed:
Load Balancing in gRPC (gRPC GitHub)
gRPC Load Balancing (gRPC blog)
gRPC on HTTP/2 Engineering a Robust, High-performance Protocol (gRPC blog)
gRPC client-side load balancing (Microsoft guide)
Java gRPC Custom Client-side load balancing (Stack Overflow)
Obviously the core pieces are a resolver and a load balancer. My use case is that I have several static, known addresses. I simply want to prioritize them as primary, secondary, etc. I believe the pick_first policy will work for this.
What I can't figure out is how to set up a custom NameResolver. I've defined a custom NameResolverProvider:
public class StaticResolverProvider extends NameResolverProvider {
#Value("${tls.enabled}")
private boolean isTlsEnabled;
#Override
protected boolean isAvailable() {
return true;
}
#Override
protected int priority() {
return 10;
}
#Override
public NameResolver newNameResolver(URI targetUri, Args args) {
return new StaticResolver();
}
#Override
public String getDefaultScheme() {
return isTlsEnabled ? "https" : "http";
}
}
and (hopefully) registered it while creating my Channel:
new NameResolverRegistry().register(new StaticResolverProvider());
Finally, here is the (currently unimplemented) NameResolver:
public class StaticResolver extends NameResolver {
#Override
public String getServiceAuthority() {
return null;
}
#Override
public void shutdown() {
}
}
These are the only two methods I see that need to be implemented. Neither of these seem to have anything to do with returning an ordered list of known addresses. The getServiceAuthority() mentions authentication, which confuses me because I don't know what the NameResolver has to do with authentication.
Please advise on what I'm missing. Thanks!
Update
I figured out the Name Resolver piece. First off, registering my resolver with my Channel looked a little different:
NameResolverRegistry.getDefaultRegistry().register(new StaticResolverProvider());
In my NameResolverProvider, I updated the getDefaultScheme() method to return "customScheme", which is the piece that would link it to my channel's call to forTarget().
The final piece was to implement the refresh() method in my NameResolver:
#Override
public void refresh() {
ResolutionResult.Builder resolutionResultBuilder = ResolutionResult.newBuilder();
List<EquivalentAddressGroup> servers = new ArrayList<>();
servers.add(new EquivalentAddressGroup(new InetSocketAddress("localhost", 50055)));
servers.add(new EquivalentAddressGroup(new InetSocketAddress("localhost", 50056)));
resolutionResultBuilder.setAddresses(Collections.unmodifiableList(servers));
listener.onResult(resolutionResultBuilder.build());
}
These are the only two methods I see that need to be implemented.
Those are the abstract ones. But the main one you need to implement is refresh() which is defined as no-op but needs to be overridden in your implementation to do anything useful. You can look at UdsNameResolver to see how refresh() is implemented and follow that pattern.
The getServiceAuthority() mentions authentication
You can ignore that for your use-case.

Grpc - Passing different objects into grpc service method

In Chat Service, we get the request from client and send a response based on it.
But My scenario is, Server has to send some different objects from a outside method of the class.
For example,
public StreamObserver<SalaryDetails> message(StreamObserver<Employee> responseObserver) {
observers.add(responseObserver);
return new StreamObserver<SalaryDetails>() {
#Override
public void onCompleted() {
observers.remove(responseObserver);
}
#Override
public void onError(Throwable arg0) {
observers.remove(responseObserver);
}
#Override
public void onNext(SalaryDetails details) {
for(StreamObserver<MetricsToVE> observer : observers) {
**observer.onNext(Employee.newBuilder()
.setName("AA")
.setCity("B")
.build());**
}
}
};
}
In below statement I have hardcoded the fields, how should I pass an object from a different method into the grpc service class.
It depends a bit on why the response values vary:
If the client can predict what the response type is based on the request, then you should probably have the client call different methods based on the type
If the possible options are well-known to the API, then you can use protobuf's oneof.
If the data is arbitrary, then you can use protobuf's Any
It seems like #2 is likely your case.

Serve static content in Spring Boot despite using #RequestMapping("**")

The context
I am currently working on an educational project. This implies two Spring Boot REST servers. One is an actual server, which does some processing.
The one I'm interested in is the other. It is a proxy which will redirect all calls to the first one. So that when I call http://localhost:8080/foo, my proxy server will in turn call http://localhost:8090/foo. And if the first server returns A, the proxy will return {"proxied": A, "someInformationAboutThisCall": B}.
I managed to get to this point with some probably inelegant but functioning code of which I give an excerpt below. The key here is that I use #RequestMapping("**") to achieve this. The next step is to design an interface that will make my additional information immediately legible, which is basically the point of this project. If I remove all #RequestMapping("**"), it works just fine.
The question
Now my problem is the following: having used #RequestMapping("**"), I cannot serve static content (the calls get redirect to the other REST server, which does not serve static content). How could I configure Spring Boot/Spring MVC to ignore resources available as static content when mapping the requests, or make the PathResourceResolver prioritary over my controller?` Or should I serve my static content from yet another JVM/server?
Thanks in advance for your help!
Edit of interest: while doing some tests, I discovered that the static content is served, with some restrictions, if I use #RequestMapping("*").
/index.html generates an error page (as does more generally any static content directly in public)
/itf/index.html works (as does more generally any file in public/itf or any other subdirectory of public)
/itf does not work: Spring Boot seems unaware of an index file in it. I must specify a full URI, down to the specific file I want to display.
This however does not work at all with #RequestMapping("**"), which I need.
The tentatives
I tried using a WebMvcConfigurerAdapter with an HandlerInterceptorAdapter (found on SO, SO again and many other places on the Internet), but could not start my project anymore because Spring boot then does not find the InterceptorRegistry bean (has there been recent changes in Spring Boot? I'm using the version 1.5.3.RELEASE).
I also tried some anti-matching but not only does it not work, it also feels very very dirty (and this whole project is probably not optimal, so that's saying a lot).
The code samples for the curious
My "proxy" controller
Note: you can suggest better ways to realize this in comments. Please keep in mind that, though I'm always open to enhancement suggestions, this was not my question.
#RestController
public class ProxyController {
#Value("${monitored.url.base}") // "http://localhost:8090"
private String redirectBase;
#RequestMapping(value = "**", method = {RequestMethod.POST, RequestMethod.PUT})
public ProxiedResponse proxifyRequestsWithBody(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
return proxifyRequest(request, headers, body);
}
#RequestMapping(value = "**")
public ProxiedResponse proxifyRequestsWithoutBody(HttpServletRequest request, #RequestHeader HttpHeaders headers) throws URISyntaxException {
return proxifyRequest(request, headers, null);
}
private ProxiedResponse proxifyRequest(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
final RequestEntity<Object> requestEntity = convertToRequestEntity(request, headers, body);
// call remote service
final ResponseEntity<Object> proxied = restTemplate.exchange(requestEntity, Object.class);
// Return service result + monitoring information
final ProxiedResponse response = new ProxiedResponse();
response.setProxied(proxied.getBody());
// set additional information
return response;
}
// Won't work properly for POST yet
private <T> RequestEntity<T> convertToRequestEntity(HttpServletRequest request, HttpHeaders headers, T body) throws URISyntaxException {
// Build proxied URL
final StringBuilder redirectUrl = new StringBuilder(redirectBase).append(request.getRequestURI());
final String queryString = request.getQueryString();
if (queryString != null) {
redirectUrl.append("?").append(queryString);
}
// TODO enhancement: transmit headers and request body to make this a real proxy
final HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
return new RequestEntity<>(body, headers, httpMethod, new URI(redirectUrl.toString()));
}
}
My dirty attempt at excluding static resources URLs
#Configuration // adding #EnableWebMvc did not solve the problem
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
private static class StaticResourcesHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String requestURI = request.getRequestURI();
if (requestURI == null || "/".equals(requestURI) || "/index.html".equals(requestURI) || requestURI.startsWith("/assets")) {
return super.preHandle(request, response, null);
}
return super.preHandle(request, response, handler);
}
}
#Autowired
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new StaticResourcesHandlerInterceptor()).addPathPatterns("/**");
}
}
You can split the path into a wild-card, and a named path variable which must match a negative lookahead regular expression.
#RequestMapping("/{variable:(?!static).*}/**")
You can then use #PathVariable String variable as an argument of your controller method to obtain the value of variable if you need to pass it.
(Would rather have written a comment but I have insufficient reputation)
Try to add the #EnableWebMvc annotation to your configuration:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
...
}

Processing GET Body with Zuul

I am using Zuul to proxy a strange client that sends a body as part of a GET request. There is unfortunately no way I can change the client.
With curl such a request can be sent as:
curl -XGET 'localhost:8765/kibana/index.html' -d' {"key": "value"}'
And the data is really sent in the body. On zuul side, however, when I try to read the body it is empty. Here is my prototype zuul code:
#Configuration
#ComponentScan
#EnableAutoConfiguration
#Controller
#EnableZuulProxy
public class ZuulServerApplication {
#Bean
public ZuulFilter myFilter() {
return new ZuulFilter(){
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request=(HttpServletRequest)ctx.getRequest();
try {
InputStream is=request.getInputStream();
String content=IOUtils.toString(is);
System.out.println("Request content:"+content);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public int filterOrder() {
return 10;
}
#Override
public String filterType() {
return "pre";
}};
}
public static void main(String[] args) {
new SpringApplicationBuilder(ZuulServerApplication.class).web(true).run(args);
}
}
If I send a POST request, the this code prints the request body without problem. However, if I send the above GET request, the body is not printed. Anything I can do to actually get the body sent as part of a GET request?
It seems that some underlying machinery[0], e.g. some built-in Zuul filter with lesser filter order, replaces default "raw" HttpServletRequest with HttpServletRequestWrapper which, under standard circumstances (i.e. not GET method with body), is able to handle multiple acquisition of input stream. But in the case of GET method with body HttpServletRequestWrapper seems to not proxy input stream at all.
Thus solution could be to change filterOrder e.g. to -10.
Then it works for the filter since HttpServletRequest is used - the mentioned machinery did not get to its turn and thus didn't replace HttpServletRequest with HttpServletRequestWrapper yet. But potential issue with this solution is that the filter might exhaust input stream for something else, e.g. filter with higher filter order. But since GET with body is not a good practice anyway, it might be good enough solution after all :)
[0] I've debug into this longer time ago, but did not get to exact point - thus vague definition of "the machinery".

Are there any restrictions in writing multiple http responses?

I am building a HTTP proxy with netty, which supports HTTP pipelining. Therefore I receive multiple HttpRequest Objects on a single Channel and got the matching HttpResponse Objects. The order of the HttpResponse writes is the same than I got the HttpRequest. If a HttpResponse was written, the next one will be written when the HttpProxyHandler receives a writeComplete event.
The Pipeline should be convenient:
final ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("writer", new HttpResponseWriteDelayHandler());
pipeline.addLast("deflater", new HttpContentCompressor(9));
pipeline.addLast("handler", new HttpProxyHandler());
Regarding this question only the order of the write calls should be important, but to be sure I build another Handler (HttpResponseWriteDelayHandler) which suppresses the writeComplete event until the whole response was written.
To test this I enabled network.http.proxy.pipelining in Firefox and visited a page with many images and connections (a news page). The problem is, that the browser does not receive some responses in spite of the logs of the proxy consider them as sent successfully.
I have some findings:
The problem only occurs if the connection from proxy to server is faster than the connection from proxy to browser.
The problem occurs more often after sending a larger image on that connection, e.g. 20kB
The problem does not occur if only 304 - Not Modified responses were sent (refreshing the page considering browser cache)
Setting bootstrap.setOption("sendBufferSize", 1048576); or above does not help
Sleeping a timeframe dependent on the responses body size in before sending the writeComplete event in HttpResponseWriteDelayHandler solves the problem, but is a very bad solution.
I found the solution and want to share it, if anyone else has a similar problem:
The content of the HttpResponse is too big. To analyze the content the whole HTML document was in the buffer. This must be splitted in Chunks again to send it properly. If the HttpResponse is not chunked I wrote a simple solution to do it. One needs to put a ChunkedWriteHandler next to the logic handler and write this class instead of the response itself:
public class ChunkedHttpResponse implements ChunkedInput {
private final static int CHUNK_SIZE = 8196;
private final HttpResponse response;
private final Queue<HttpChunk> chunks;
private boolean isResponseWritten;
public ChunkedHttpResponse(final HttpResponse response) {
if (response.isChunked())
throw new IllegalArgumentException("response must not be chunked");
this.chunks = new LinkedList<HttpChunk>();
this.response = response;
this.isResponseWritten = false;
if (response.getContent().readableBytes() > CHUNK_SIZE) {
while (CHUNK_SIZE < response.getContent().readableBytes()) {
chunks.add(new DefaultHttpChunk(response.getContent().readSlice(CHUNK_SIZE)));
}
chunks.add(new DefaultHttpChunk(response.getContent().readSlice(response.getContent().readableBytes())));
chunks.add(HttpChunk.LAST_CHUNK);
response.setContent(ChannelBuffers.EMPTY_BUFFER);
response.setChunked(true);
response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
}
}
#Override
public boolean hasNextChunk() throws Exception {
return !isResponseWritten || !chunks.isEmpty();
}
#Override
public Object nextChunk() throws Exception {
if (!isResponseWritten) {
isResponseWritten = true;
return response;
} else {
HttpChunk chunk = chunks.poll();
return chunk;
}
}
#Override
public boolean isEndOfInput() throws Exception {
return isResponseWritten && chunks.isEmpty();
}
#Override
public void close() {}
}
Then one can call just channel.write(new ChunkedHttpResponse(response) and the chunking is done automatically if needed.

Resources