I'm trying to set an option on JsConfig for a single async method on JsonServiceClient by using JsConfigScope, but it does not seem to work. What am I doing wrong? Is there another way to do this?
var client = new JsonServiceClient(baseUrl);
using (var scope = JsConfig.BeginScope())
{
scope.EmitCamelCaseNames = true;
return client.PostAsync<SomeResponse>(url, request);
}
You can't use a using scope with an async request since the scope will be disposed before the async service has completed. You would need to await the response, i.e:
using (JsConfig.With(new Config { TextCase = TextCase.CamelCase }))
{
return await client.PostAsync<SomeResponse>(url, request);
}
You can use the lower-level HTTP Utils to split the request serialization and response deserialization outside of the async call which is the approach used in the StripeGateway.
Otherwise you could potentially use the Response filter to dispose of the scoped configuration, e.g:
var scope = JsConfig.With(new Config { TextCase = TextCase.CamelCase });
var client = new JsonServiceClient(BaseUrl) {
ResponseFilter = httpRes => scope.Dispose()
};
return client.PostAsync<SomeResponse>(url, request);
Related
I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.
In AppHost.Configure I set a global JSON config JsConfig.TreatEnumAsInteger = false; and have a simple handler with two GET endpoints
public object Get(GetDayOfWeekAsText request)
{
return new GetDayOfWeekResponse();
}
public object Get(GetDayOfWeekAsInt request)
{
return new HttpResult(new GetDayOfWeekResponse())
{
ResultScope = () => JsConfig.With(new Config
{
TreatEnumAsInteger = true
})
};
}
Depending on the request I call first all subsequent requests will serialize enums as text or integer until the application is recycled. Explicitly setting TreatEnumAsInteger in GetDayOfWeekAsText has no effect.
Thanks!
This should now be resolved with this commit.
This change is available from v5.4.1 that's now available on MyGet.
I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.
The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}
I have a client app which monitors the changes in real-time by establishing a long-live HTTP connection to server.
In ASP.NET WebAPI, the server can take use PushStreamContent to keep the connection for a long time and send response once there is an update.
But in ServiceStack, seems there is no similar stuff.
I looked at the sample code of Different ways of returning an ImageStream
IStreamWriter.WriteTo method is only called once, and I can't use async IO operation to avoid blocking server thread.
Is there a way to send progressive response to client asynchronously?
here is sample code in WebAPI which does the job
public static async Task Monitor(Stream stream, HttpContent httpContent, TransportContext transportContext)
{
ConcurrentQueue<SessionChangeEvent> queue = new ConcurrentQueue<SessionChangeEvent>();
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Action<SessionChangeEvent> callback = (evt) =>
{
queue.Enqueue(evt);
tcs.TrySetResult(null);
};
OnSessionChanged += callback;
try
{
using (StreamWriter sw = new StreamWriter(stream, new UTF8Encoding(false)))
{
await sw.WriteLineAsync(string.Empty);
await sw.FlushAsync();
await stream.FlushAsync();
for (; ; )
{
Task task = tcs.Task;
await Task.WhenAny(task, Task.Delay(15000));
if (task.Status == TaskStatus.RanToCompletion)
{
tcs = new TaskCompletionSource<object>();
SessionChangeEvent e;
while (queue.TryDequeue(out e))
{
string json = JsonConvert.SerializeObject(e);
await sw.WriteLineAsync(json);
}
task.Dispose();
}
else
{
// write an empty line to keep the connection alive
await sw.WriteLineAsync(string.Empty);
}
await sw.FlushAsync();
await stream.FlushAsync();
}
}
}
catch (CommunicationException ce)
{
}
finally
{
OnSessionChanged -= callback;
}
}
Writing to a long-running connection is exactly what Server Events does. You can look at the implementation for ServerEventsHandler or ServerEventsHeartbeatHandler to see it's implemented in ServiceStack.
Basically it just uses a custom ASP.NET IHttpAsyncHandler which can be registered at the start of ServiceStack's Request Pipeline with:
appHost.RawHttpHandlers.Add(req => req.PathInfo.EndsWith("/my-stream")
? new MyStreamHttpHandler()
: null);
Where MyStreamHttpHandler is a custom HttpAsyncTaskHandler, e.g:
public class MyStreamHttpHandler : HttpAsyncTaskHandler
{
public override bool RunAsAsync() { return true; }
public override Task ProcessRequestAsync(
IRequest req, IResponse res, string operationName)
{
//Write any custom request filters and registered headers
if (HostContext.ApplyCustomHandlerRequestFilters(req, res))
return EmptyTask;
res.ApplyGlobalResponseHeaders();
//Write to response output stream here, either by:
res.OuputStream.Write(...);
//or if need access to write to underlying ASP.NET Response
var aspRes = (HttpResponseBase)res.OriginalResponse;
aspRes.OutputStream...
//After you've finished end the request with
res.EndHttpHandlerRequest(skipHeaders: true);
return EmptyTask;
}
}
The ApplyCustomHandlerRequestFilters() and ApplyGlobalResponseHeaders() at the start gives other plugins a chance to validate/terminate the request or add any HTTP Headers (e.g. CorsFeature).
Have a look at ServerEvents. If I understood you right, this is what you are looking for.
Is there any way to add the type of the response dto to the rabbitmq response message's headers collection?
(My consumer is using spring's rabbitmq handler which seems to depend on explicit type information inside the mq header when deserializing.)
Currently servicestack's mq producer already returns serveral headers, such as "content_type='application/json".
I am in need of an additional header, e.g. "typeId"="HelloResponse", so that the consuming web app knows how to deserialize the message, even in RPC cases where the response queue name is some kind of GUID.
Is there some kind of configuration which would enable me to archieve such an behaviour? Or some hook before the message gets published so that I can add the header myself?
I've added support for automatically populating the Message Body Type in RabbitMQ's IBasicProperties.Type as well as adding support for both Publish and GetMessage Filters in this commit.
Here's an example of configuring a RabbitMqServer with custom handlers where you can modify the message and its metadata properties when its published and received:
string receivedMsgApp = null;
string receivedMsgType = null;
var mqServer = new RabbitMqServer("localhost")
{
PublishMessageFilter = (queueName, properties, msg) => {
properties.AppId = "app:{0}".Fmt(queueName);
},
GetMessageFilter = (queueName, basicMsg) => {
var props = basicMsg.BasicProperties;
receivedMsgType = props.Type; //automatically added by RabbitMqProducer
receivedMsgApp = props.AppId;
}
};
mqServer.RegisterHandler<Hello>(m =>
new HelloResponse { Result = "Hello, {0}!".Fmt(m.GetBody().Name) });
mqServer.Start();
Once Configured any message published or received will go through the above handlers, e.g:
using (var mqClient = mqServer.CreateMessageQueueClient())
{
mqClient.Publish(new Hello { Name = "Bugs Bunny" });
}
receivedMsgApp.Print(); // app:mq:Hello.In
receivedMsgType.Print(); // Hello
using (IConnection connection = mqServer.ConnectionFactory.CreateConnection())
using (IModel channel = connection.CreateModel())
{
var queueName = QueueNames<HelloResponse>.In;
channel.RegisterQueue(queueName);
var basicMsg = channel.BasicGet(queueName, noAck: true);
var props = basicMsg.BasicProperties;
props.Type.Print(); // HelloResponse
props.AppId.Print(); // app:mq:HelloResponse.Inq
var msg = basicMsg.ToMessage<HelloResponse>();
msg.GetBody().Result.Print(); // Hello, Bugs Bunny!
}
This change is available from ServiceStack v4.0.33+ that's now available on MyGet.