Spring Boot GRPC: ServerIntereceptor to read data in the request, and set it in the response - multithreading

There is a field called "metadata" (not to be confused with GRPC metadata) that is present in every request proto that comes to the GRPC service:
message MyRequest {
RequestResponseMetadata metadata = 1;
...
}
And the same field is also present in all responses:
message MyResponse {
RequestResponseMetadata metadata = 1;
...
}
I am trying to write a ServerInterceptor (or something else, if it works) to read the "metadata" field from the request, keep it somewhere, and then set it in the response once done processing the request.
Attempt 1: ThreadLocal
public class ServerInterceptor implements io.grpc.ServerInterceptor {
private ThreadLocal<RequestResponseMetadata> metadataThreadLocal = new ThreadLocal<>();
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
final Metadata requestHeaders,
ServerCallHandler<ReqT, RespT> next) {
return new SimpleForwardingServerCallListener<ReqT>(
next.startCall(
new SimpleForwardingServerCall<ReqT, RespT>(call) {
#Override
public void sendMessage(RespT message) {
super.sendMessage(
(RespT)
MetadataUtils.setMetadata(
(GeneratedMessageV3) message, metadataThreadLocal.get()));
metadataThreadLocal.remove();
}
},
requestHeaders)) {
#Override
public void onMessage(ReqT request) {
// todo nava see if ReqT can extend GenericV3Message
var metadata = MetadataUtils.getMetadata((GeneratedMessageV3) request);
metadataThreadLocal.set(metadata);
super.onMessage(request);
}
};
}
}
I tried to use ThreadLocal, to later realise that sendMessage and onMessage need not necessary to be on the same thread.
Attempt 2: GRPC Context
public class ServerInterceptor implements io.grpc.ServerInterceptor {
public static final Context.Key<RequestResponseMetadata> METADATA_KEY = Context.key("metadata");
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
final Metadata requestHeaders,
ServerCallHandler<ReqT, RespT> next) {
return new SimpleForwardingServerCallListener<ReqT>(
next.startCall(
new SimpleForwardingServerCall<ReqT, RespT>(call) {
#Override
public void sendMessage(RespT message) {
super.sendMessage(
(RespT)
MetadataUtils.setMetadata(
(GeneratedMessageV3) message, METADATA_KEY.get()));
}
},
requestHeaders)) {
#Override
public void onMessage(ReqT request) {
var metadata = MetadataUtils.getMetadata((GeneratedMessageV3) request);
var newContext = Context.current().withValue(METADATA_KEY, metadata);
oldContext = newContext.attach();
super.onMessage(request);
}
};
}
}
I am planning to detach the context in a onComplete(), but before it reaches there itself, METADATA_KEY.get() in sendMessage returns null, while I was expecting it to return the data.
Even before hitting the sendMessage() function, I get this in the console, indicating that I am doing something wrong:
3289640 [grpc-default-executor-0] ERROR i.g.ThreadLocalContextStorage - Context was not attached when detaching
java.lang.Throwable: null
at io.grpc.ThreadLocalContextStorage.detach(ThreadLocalContextStorage.java:48)
at io.grpc.Context.detach(Context.java:421)
at io.grpc.Context$CancellableContext.detach(Context.java:761)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:39)
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
How do I read data when a request is received, store it somewhere and use it when the response is send back?

You can use Metadata to pass values from the request to the response:
public class MetadataServerInterceptor implements ServerInterceptor {
public static final Metadata.Key<byte[]> METADATA_KEY = Metadata.Key.of("metadata-bin", Metadata.BINARY_BYTE_MARSHALLER);
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
var serverCall = new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
#Override
public void sendMessage(RespT message) {
byte[] metadata = headers.get(METADATA_KEY);
message = (RespT) MetadataUtils.setMetadata((GeneratedMessageV3) message, metadata);
super.sendMessage(message);
}
};
ServerCall.Listener<ReqT> listenerWithContext = Contexts.interceptCall(Context.current(), serverCall, headers, next);
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(listenerWithContext) {
#Override
public void onMessage(ReqT message) {
byte[] metadata = MetadataUtils.getMetadata((GeneratedMessageV3) message);
headers.put(METADATA_KEY, metadata);
super.onMessage(message);
}
};
}
}
Note: Since it is not possible to put the instance of RequestResponseMetadata in the metadata (at least without implementing a custom marshaller), you can save it there as a byte array. You can use toByteArray() on your RequestResponseMetadata object to get byte[] and RequestResponseMetadata.#parseFrom(byte[]) to get the object from byte[].

Related

How to wait for response in request inside other request async

I would like to know how to deal with my problem. I am trying to send request in android studio (using retrofit2), and in "onResponse" method send other request which assing List to object from the first request. Problem is that the first request finish before the second can download the data and assign empty list. Some spaghetti i know, but i hope that code will help to understand my problem.
First request method
private void getTrainingPlans()
{
INodeJS inter= RetrofitClient.getGsonInstance().create(INodeJS.class);
retrofit2.Call<List<PlanTreningowy>> call=inter.getTrainingPlans(User.getId());
call.enqueue(new Callback<List<PlanTreningowy>>() {
#Override
public void onResponse(Call<List<PlanTreningowy>> call, Response<List<PlanTreningowy>> response) {
if(!response.isSuccessful())
{
Toast.makeText(getContext(),"Cant download data",Toast.LENGTH_SHORT).show();
return;
}
if (response.body() != null)
{
plany_treningowe=response.body();
for(PlanTreningowy plan:plany_treningowe)
{
//second request method
getExercisesFromTrainingPlan(plan.id);
//trying assign data from request to object but its empty
plan.exercises= cwiczenia_plan_treningowy;
}
nazwa_planu.setText(plany_treningowe.get(0).getTytul());
opis_planu.setText(plany_treningowe.get(0).getOpis());
//getExercisesName(plany_treningowe.get(0).getExercises());
TreningAdapter treningAdapter=new TreningAdapter(getContext(),t_nazwa, t_nazwa.size(),PlanTreningowyFragment.this);
recyclerView.setAdapter(treningAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
}
else
{
nazwa_planu.setText("Brak planów");
opis_planu.setText("Brak");
}
}
#Override
public void onFailure(Call<List<PlanTreningowy>> call, Throwable t) {
}
});
}
Second request method (nested)
Call<List<Exercise>> call=inter.getExercisesFromTrainingPlan(id_planu);
call.enqueue(new Callback<List<Exercise>>() {
#Override
public void onResponse(Call<List<Exercise>> call, Response<List<Exercise>> response) {
if(!response.isSuccessful())
{
Toast.makeText(getContext(),"Nie udało się wczytać ćwiczeń",Toast.LENGTH_SHORT).show();
return;
}
if (response.body() != null)
{ //assign data to list
cwiczenia_plan_treningowy=response.body();
Log.e("dlugosc" , String.valueOf(cwiczenia_plan_treningowy.size()));
return;
}
}
#Override
public void onFailure(Call<List<Exercise>> call, Throwable t) {
}
}); ```

Azure Functions 3 and [FromBody] modelbinding

I am creating a post endpoint using Azure Functions version 3. In Asp.net it is very convenient to get the post object using the [FromBody] tag and the magic will happen with modelbinding.
Is there a way to use the FromBody tag in Azure Functions v3?
Yes you can do that,
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")][FromBody] User user, ILogger log, ExecutionContext context)
Here is an Example
Microsoft.Azure.Functions.Worker version 1.7.0-preview1 makes custom input conversion possible. The below will convert HttpRequestData.Body to a POCO via converting the stream to a byte array then passing the byte array back into the normal input converter process (where it is convertered by the built-in JsonPocoConverter. It relies on reflection as the services required to delegate the conversion after converting the stream to a byte array are internal, so it may break at some point.
Converter:
internal class FromHttpRequestDataBodyConverter : IInputConverter
{
public async ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
{
if (context.Source is null
|| context.Source is not HttpRequestData req
|| context.TargetType.IsAssignableFrom(typeof(HttpRequestData)))
{
return ConversionResult.Unhandled();
}
var newContext = new MyConverterContext(
context,
await ReadStream(req.Body));
return await ConvertAsync(newContext);
}
private static async Task<ReadOnlyMemory<byte>> ReadStream(Stream source)
{
var byteArray = new byte[source.Length];
using (var memStream = new MemoryStream(byteArray))
{
await source.CopyToAsync(memStream);
}
return byteArray.AsMemory();
}
private static ValueTask<ConversionResult> ConvertAsync(MyConverterContext context)
{
// find the IInputConversionFeature service
var feature = context.FunctionContext
.Features
.First(f => f.Key == InputConvertionFeatureType)
.Value;
// run the default conversion
return (ValueTask<ConversionResult>)(ConvertAsyncMethodInfo.Invoke(feature, new[] { context })!);
}
#region Reflection Helpers
private static Assembly? _afWorkerCoreAssembly = null;
private static Assembly AFWorkerCoreAssembly => _afWorkerCoreAssembly
??= AssemblyLoadContext.Default
.LoadFromAssemblyName(
Assembly.GetExecutingAssembly()
.GetReferencedAssemblies()
.Single(an => an.Name == "Microsoft.Azure.Functions.Worker.Core"))
?? throw new InvalidOperationException();
private static Type? _inputConversionFeatureType = null;
private static Type InputConvertionFeatureType => _inputConversionFeatureType
??= AFWorkerCoreAssembly
.GetType("Microsoft.Azure.Functions.Worker.Context.Features.IInputConversionFeature", true)
?? throw new InvalidOperationException();
private static MethodInfo? _convertAsyncMethodInfo = null;
private static MethodInfo ConvertAsyncMethodInfo => _convertAsyncMethodInfo
??= InputConvertionFeatureType.GetMethod("ConvertAsync")
?? throw new InvalidOperationException();
#endregion
}
Concrete ConverterContext class:
internal sealed class MyConverterContext : ConverterContext
{
public MyConverterContext(Type targetType, object? source, FunctionContext context, IReadOnlyDictionary<string, object> properties)
{
TargetType = targetType ?? throw new ArgumentNullException(nameof(context));
Source = source;
FunctionContext = context ?? throw new ArgumentNullException(nameof(context));
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
}
public MyConverterContext(ConverterContext context, object? source = null)
{
TargetType = context.TargetType;
Source = source ?? context.Source;
FunctionContext = context.FunctionContext;
Properties = context.Properties;
}
public override Type TargetType { get; }
public override object? Source { get; }
public override FunctionContext FunctionContext { get; }
public override IReadOnlyDictionary<string, object> Properties { get; }
}
Service configuration:
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.Configure<WorkerOptions>((workerOptions) =>
{
workerOptions.InputConverters.Register<Converters.FromHttpRequestDataBodyConverter>();
});
})
.Build();
host.Run();
}
}

When are we getting the exact message "timeout" in Retrofit onFailure()?

I got a SocketTimeoutException. In the onFailure method, when I call t.getMessage(), it returns exactly "timeout"
Here is my code:
private void getCouponsService() {
APIInterface mObjInterface = APIClient.getClient().create(APIInterface.class);
Call<CouponsResponseModel> call = mObjInterface.getCoupons();
call.enqueue(new Callback<CouponsResponseModel>() {
#Override
public void onResponse(Call<CouponsResponseModel> call, Response<CouponsResponseModel> response) {
if (response.code() == 200) {
// Handle success response
}
}
#Override
public void onFailure(Call<CouponsResponseModel> call, Throwable t) {
String message = t.getMessage();
}
});
}
The value of message is exactly "timeout". In which scenario are we supposed to get that message? I expected the output to be Unable to resolve host "abc.domainname.in": No address associated with hostname.

Botframework how log history

Do You have any idea how to log all outgoing/incoming messages? I am not sure how to capture outgoing messages.
I use Chains and Forms.
For example
await Conversation.SendAsync(activity, rootDialog.BuildChain);
AND
activity.CreateReply(.....);
I found better solution
public class BotToUserLogger : IBotToUser
{
private readonly IMessageActivity _toBot;
private readonly IConnectorClient _client;
public BotToUserLogger(IMessageActivity toBot, IConnectorClient client)
{
SetField.NotNull(out _toBot, nameof(toBot), toBot);
SetField.NotNull(out _client, nameof(client), client);
}
public IMessageActivity MakeMessage()
{
var toBotActivity = (Activity)_toBot;
return toBotActivity.CreateReply();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
await _client.Conversations.ReplyToActivityAsync((Activity)message, cancellationToken);
}
}
public class BotToUserDatabaseWriter : IBotToUser
{
private readonly IBotToUser _inner;
public BotToUserDatabaseWriter(IBotToUser inner)
{
SetField.NotNull(out _inner, nameof(inner), inner);
}
public IMessageActivity MakeMessage()
{
return _inner.MakeMessage();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
// loging outgoing message
Debug.WriteLine(message.Text);
//TODO log message for example into DB
await _inner.PostAsync(message, cancellationToken);
}
In controller use
public MessagesController()
{
var builder = new ContainerBuilder();
builder.RegisterType<BotToUserLogger>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Register(c => new BotToUserTextWriter(c.Resolve<BotToUserLogger>()))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.Update(Microsoft.Bot.Builder.Dialogs.Conversation.Container);
}
Its look like I cant log outgoing message.
I changed SDK source code.
Add event in Conversations.cs
For example like this.
public delegate void MessageSendedEventHandler(object sender, Activity activity, string conversationId);
public static event MessageSendedEventHandler MessageSended;
And add in every Send....HttpMessagesAsync method
this MessageSended?.Invoke(this, activity, conversationId);
its not great solution. But its working

return data progressively from ServiceStack API

Currently my app returns data by MemoryStream, the problem is
the size of data could be large than 500MB, and that takes up much memory before return.
I am seeking for a way to return the data progressively.
For example, flush the output for every 1MB.
First I tried IPartialWriter
public class ViewRenderResult : IPartialWriter
{
public void WritePartialTo(IResponse response)
{
response.Write("XXX");
}
public bool IsPartialRequest { get { return true; } }
}
response.Write can only be called for one time.
Then I found IStreamWriter
public interface IStreamWriter
{
void WriteTo(Stream responseStream);
}
I doubt it caches all data before returining.
Please can anyone clarify it?
This previous answer shows different response types ServiceStack supports, e.g. you can just return a Stream or write to the base.Response.OutputStream directly from within your Service.
These ImageServices also shows the different ways you can write a binary response like an Image to the response stream, e.g. here's an example of using a custom IStreamWriter which lets you control how to write to the Response OutputStream:
//Your own Custom Result, writes directly to response stream
public class ImageResult : IDisposable, IStreamWriter, IHasOptions
{
private readonly Image image;
private readonly ImageFormat imgFormat;
public ImageResult(Image image, ImageFormat imgFormat = null)
{
this.image = image;
this.imgFormat = imgFormat ?? ImageFormat.Png;
this.Options = new Dictionary<string, string> {
{ HttpHeaders.ContentType, this.imgFormat.ToImageMimeType() }
};
}
public void WriteTo(Stream responseStream)
{
using (var ms = new MemoryStream())
{
image.Save(ms, imgFormat);
ms.WriteTo(responseStream);
}
}
public void Dispose()
{
this.image.Dispose();
}
public IDictionary<string, string> Options { get; set; }
}
Which you can return in your Service with:
public object Any(ImageAsCustomResult request)
{
var image = new Bitmap(100, 100);
using (var g = Graphics.FromImage(image))
{
g.Clear(request.Format.ToImageColor());
return new ImageResult(image, request.Format.ToImageFormat());
}
}

Resources