You should be able to access the response headers in the ResponseFilter
(or static GlobalResponseFilter
).
Also if you just want to access the response and headers and youāre ok with deserializing the response yourself you can just call the url without return type, e.g:
using (HttpWebResponse webRes = client.Get(requestUri))
{
//webRes.Headers[]
var json = webRes.GetResonseStream().ReadFully().FromUtf8Bytes();
webRes.Close();
}
Is this a close enough example of how to do our own serialization? https://github.com/ServiceStack/ServiceStack/blob/e392265456e12077087c1cd014c918913f409bc9/src/ServiceStack.Client/ServiceClientBase.cs#L1639
However, it ode snot handle gzip or deflate compression responses.
Is there any code that does what we want. I dont really want to have to know all the permutations of what we could get back to deserialize.
GetResponse<T>
is what the Service Client uses to deserialize the HttpWebResponse into a the target TResponse type.
It doesnāt need to handle gzip/deflate compression as itās already handled by .NET WebRequest once AutomaticCompression is enabled (which it is by default).
But GetResponse<T>
is private, are you inheriting ServiceClientBase? i.e. did you need for me to make it protected?
protected for GetResponse<TResponse>()
would be good for the JSC for another reason.
But actually right now, I am having to convert the client.Get<HttpWebResponse>(requestUri)
in my ClientCache class, since the ResultsFilter delegate needs it to return value that is typeof(TResponse)
, and I need to call client.Get<HttpWebResponse>(requestUri)
to read the headers as well.
Iām pushing through the change to make GetResponse<T>
public can take up to 30mins before it will reach MyGet.
Weāre currently having to do some DNS configuration with forums now so there could be some downtime with forums.servicestack.net which could last a while depending on how quick the DNS servers take to propagate changes.
GetResponse<T>
is now protected and on v4.0.55 on MyGet.
Making the DNS changes nowā¦
Thanks,
Can you give me some guidance on what I need to do to deserialize the response from this:
(given a requestUri, and the Type for the responseType (as we have given to us in the ResultsFilterDelegate?)
HttpWebResponse webRes = client.Get<HttpWebResponse>(requestUri))
Iāll would need to be able to deal with all the different types of responses that could be returned.
(BTW: Is all that knowledge encapsulated in ServiceClientBase.GetResponse?)
ServiceClientBase just uses GetResponse<T>
to convert the HTTP Response into the target response type.
OK, so what I am hearing is that:
(Within my ServiceCache class)
I should do the same (cover the same cases) as ServiceClientBase.GetResponse() does.
But, of course, in a non-generic flavor of that code, becuase I only have the type, I donāt have the generic, to play with.
FYI Iāve just added richer server support for Caching Headers and NotModified Responses which will let you return your response dto decorated in HttpResult
, example usage:
return new HttpResult(response) {
ETag = CalculateETag(response),
MaxAge = TimeSpan.FromHours(24), //Optional, falls back to HttpCacheFeature.DefaultMaxAge (1hr)
CacheControl = CacheControl.Public | CacheControl.NoStore | CacheControl.MustRevalidate,
};
The Typed Caching Headers now available on HttpResult include:
public string ETag { get; set; }
public TimeSpan? Age { get; set; }
public TimeSpan? MaxAge { get; set; }
public DateTime? Expires { get; set; }
public DateTime? LastModified { get; set; }
public CacheControl CacheControl { get; set; }
The server functionality is maintained in HttpCacheFeature.cs whilst the tests are available in CacheFeatureTests.cs - the tests should provide a good description of the whatās currently supported, i.e. it automatically handles returning 304ās with valid If-None-Match
or If-Modified-Since
headers.
The HttpCacheFeature
plugin is registered by default, so you can customize it by retrieving and modifying the plugin in AppHost, e.g:
this.GetPlugin<HttpCacheFeature>().DefaultMaxAge = TimeSpan.FromMinutes(1);
Or the built-in caching functionality can be disabled entirely with:
this.Plugins.RemoveAll(x => x is HttpCacheFeature);
This change is available from v4.0.55 thatās now available on MyGet.
Thereās no caching support available in any .NET clients yet, but Iāll look into creating something this week as time permits.
Thatās quite nice on the server side.
We used a ResponseFilterAttribute called [HttpClientShouldCacheResponseAttribute(CacheDuration.VeryShort|Short|Medium|Long|VeryLong)]
to pretty much do the same thing declaratively on each service operation, and defined the duration ranges of expiry in our deployment configuration.
This attribute also sets the ETag header by reading the calculated ETag value from the cached value in the current ICacheClient (based on the cachekey calculated for the request). The actual ETag is created by each service operation, iff it is declared to cache its response, which we do with another attribute [CacheResponse(CacheDuration, CacheRepresentation.PerUser|AllUsers)]
What we discovered is that the TTL for the cached response (on server side) is not desirable to be the same length as the TTL that clients should cache the response for, since clients are performing HTTP validation (with ETag or LastModified) and the service can reliably manage its own cache updates (via POST, PUT, DELETE etc). So we separated them out.
We also discovered that we needed some service operations to cache different representations per user, and others for every user. So the declarative response attributes work real well for us.
ok so youāre relying on using ICacheClient
to maintain ETags for responses.
This approach doesnāt use the cache, but does require the Service implementation to calculate the ETag or LastModified for the requested resource (as itās service specific). The Request.HasValidCache()
lets you short-circuit the request saving Service Execution if youāre able to cheaply calculate the ETag or LastModified for the request otherwise returning the decorated HttpResult
requires constructing the Response DTO so doesnāt save on Service Execution, but does save on serialization + bandwidth so cache-aware clients should still see an improvement.
It uses the Cache for existing ToOptimizedCache()
responses where it saves the date the cache was created so it can respond to If-Modified-Since
requests which should also save serialization + bandwidth for cache-aware clients.
FYI Iāve added a CachedServiceClient in this commit.
It should work with any ServiceClientBase
ServiceClient and implements IServiceClient
so should be an easy drop-in replacement where you can register it with either:
container.Register(c => new JsonServiceClient(baseUrl).WithCache());
//equivalent to:
container.Register<IServiceClient>(c =>
new CachedServiceClient(new JsonServiceClient(baseUrl)));
In order to make it work with the least friction Iāve added a new NotModifiedFilter which lets you add a hook to catch 304 responses without needing to add try/catch around each call.
Iām going to add a few more tests and support for async requests which shouldnāt affect its external API, but hopefully youāll find some of the new additions useful. This change is available from v4.0.55 thatās now available on MyGet.
Async support and more tests added in this commit should now be in a good state for adoption. MyGet also updated with latest changes.
Could you possibly enhance CachedServiceClient to allow the cache storage to be injected? Possibly using the ICacheClient interface. In a server to server scenario it would be particularly useful to cache out of process.
I can make the ConcurrentDictionary injectable but not replace the cache used as this would be primarily used on clients where it currently serves a majority of the use cases efficiently, refactoring to support ICacheClient would add serialization and I/O latency overhead for no gain and the impl would be awkwardly different. If you need it, you can copy the class and approach used to create your own high-level cache client which is likely what I need to do in order to support JsonHttpClient if thereās demand for it.
Ok, thanks for the explanation and advice.
Iāve extracted HttpCacheEntry
and added overloads so ConcurrentDictionary<string, HttpCacheEntry>
cache is now injectable in the constructor or replaceable with SetCache()
.
Iāve also added a new CachedHttpClient implementation for providing the same functionality for JsonHttpClient
which can be used with either:
container.Register(c => new JsonHttpClient(baseUrl).WithCache());
//equivalent to:
container.Register<IServiceClient>(c =>
new CachedHttpClient(new JsonHttpClient(baseUrl)));
The cache client implementations can be easily substituted as seen in the shared CachedServiceClientTests.cs where they both implement a shared ICachedServiceClient
interface which extends IServiceClient
with some cache client specific stats and APIs.
These changes are available from v4.0.55 thatās now available on MyGet.
Hey Mythz,
Sometime just over 24hrs ago, v55 broke our existing implementations of Http expiration, most likely on the service side.
Two problems have been identified.
- For some reason http responses for our services now return the āCache-Control: max-age=3600ā, even though we have response headers which set it to āCache-Control: no-cache, max-age=300ā
- We are getting (across most integration tests) an exception: ServiceStack.WebServiceException: 400 - Bad Request: Bad Request: An item with the same key has already been added. Bad Request
I havenāt analysed the latest changes in v55 in the last 24 hours, but given the two problems we are seeing now, I suspect (at this stage) that the exception might have something to do with us adding the 'Cache-Controlāor āExpiresā expiration headers to the Response headers collection, and it already being there?
Would you know what might have changed and where?