I have rather bizarre behaviour in my MVC web application.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class NoCacheFilterAttribute : ActionFilterAttribute
{
ctx.Response.AddHeader("Cache-Control", "no-cache);
}
I am adding this filter class to GlobalFilterCollection so that it runs on every action.
I am attempting to override this header selectively in one of my home controller action method.
Response.Headers.Remove("Cache-Control");
Response.AddHeader("Cache-Control", "private,must-revalidate,proxy-revalidate ");
Problem is non these code are setting the correct values. When I inspect the values in response header the Cache-Control is set to "private, s-maxage=0". I scanned all my code to see if I was explicitly doing so anywhere but I don't see myself doing that. The odd thing is If I call
Response.Cache.SetNoStore(); or Any methods of Response.Cache. the response value of Cache-Control then changes. I am not sure why Response.AddHeader or AppendHeader doesn't work?
The "Cache-Control" response header in ASP.NET is special and shouldn't be set explicitly. Instead, use the first-class APIs hanging off of the Response.Cache object as you've already found.
Could you solve it by adding some conditional logic before performing ctx.Response.AddHeader("Cache-Control", "no-cache)?
Basically, you could create another filter named [ApplyCache] to apply to your action method for which you don't want no-cache set, and in NoCacheFilterAttribute, you can do this,
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class NoCacheFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext ctx)
{
if (!ctx.ControllerContext.ControllerDescriptor.GetFilters().Contains(new ApplyCache()) && !ctx.ActionDescriptor.GetFilters().Contains(new ApplyCache())
{
ctx.Response.Headers.Add("Cache-Control", "no-cache");
}
}
}
For MVC this could be,
public class ApplyCache: System.Web.Mvc.ActionFilterAttribute
{
}
public class NoCache: System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
if (!actionExecutedContext.ActionDescriptor.IsDefined(typeof(ApplyCache), true))
{
actionExecutedContext.RequestContext.HttpContext.Response.AddHeader("Cache-Control", "no-cache");
}
}
}
and you want to add both filters to your global collection.
public static void RegisterMvcFilters(GlobalFilterCollection filters)
{
filters.Add(new ApplyCache());
filters.Add(new NoCache());
}
OK. This is what I found out. Any time anywhere in your code if you use (any other NuGet library may be setting the headers) following code:
Response.Cache.SetNoStore(); //or any other .SetXXX; methods
, the headers are written to buffer, and if you issue say following code (some examples using this in ActionFilterAttribute):
HttpContext ctx = HttpContext.Current;
ctx.Response.AppendHeader("Cache-Control", "private, must-revalidate, proxy-revalidate ");
ctx.Response.AddHeader("Cache-Control", "private, must-revalidate, proxy-revalidate ");
ctx.Response.Headers.Set("Cache-Control", "no-cache, no-store, must-revalidate, proxy-revalidate");
to modify the header, the header values are not written. Only way to reset the header is to issue following line of code and then the issue the code above or call Response.Cache.SetXXX methods.
ctx.Response.ClearHeaders();
The Response.Cache does not provide a mechanism to reset the response headers values that were set using .SetXXXX methods. If Response.Cache.SetXXX method was never called then calling Response.AppendHeader, AddHeader or Headers.Set will work without any need to call Response.ClearHeaders();
Related
I've created a custom locale resolver for the purpose of internationalization of quarkus rest application. Since I'm new to quarkus, could you please let me know where should I write the following bootstrap code in the application,
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.localeResolver(customlocaleresolver)
.locales(Locale.GERMAN, Locale.FRENCH, Locale.ITALIAN)
.defaultLocale(Locale.ENGLISH)
.buildValidatorFactory();
return validatorFactory.getValidator();
Also, how should I make controller class to know that locale has to be resolved before sending the response.Any help is greatly appreciated. Thanks
I referred this link for internationalization - https://in.relation.to/2020/01/23/hibernate-validator-6-1-1-released/
Adding the custom locale resolver, getting null for httpheader from resteasycontext.
public class ResteasyContextLocaleResolver implements LocaleResolver {
#Override
public Locale resolve(LocaleResolverContext context) {
Optional<List<Locale.LanguageRange>> localePriorities = getAcceptableLanguages();
if (!localePriorities.isPresent()) {
return context.getDefaultLocale();
}
List<Locale> resolvedLocales = Locale.filter(localePriorities.get(), context.getSupportedLocales());
if (resolvedLocales.size() > 0) {
return resolvedLocales.get(0);
}
return context.getDefaultLocale();
}
private Optional<List<Locale.LanguageRange>> getAcceptableLanguages() {
HttpHeaders httpHeaders = ResteasyContext.getContextData(HttpHeaders.class);
if (httpHeaders != null) {
List<String> acceptLanguageList = httpHeaders.getRequestHeader("Accept-Language");
if (acceptLanguageList != null && !acceptLanguageList.isEmpty()) {
return Optional.of(Locale.LanguageRange.parse(acceptLanguageList.get(0)));
}
}
return Optional.empty();
}
}
This is documented in the Hibernate Validator guide for Quarkus:
You can configure this behavior by adding the following configuration in your application.properties:
# The default locale to use
quarkus.default-locale=fr-FR
If you are using RESTEasy Reactive, in the context of a JAX-RS endpoint, Hibernate Validator will automatically resolve the optimal locale to use from the Accept-Language HTTP header, provided the supported locales have been properly specified in the application.properties:
# The list of all the supported locales
quarkus.locales=en-US,es-ES,fr-FR
If the built-in feature that resolves the locale from the Accept-Language HTTP header suits your needs, then you do not need a custom locale resolver.
If that built-in feature isn't enough for some reason, you can declare your locale resolver as a CDI bean and it should get automatically picked up:
#ApplicationScoped
public class MyLocaleResolver implements LocaleResolver {
// ...
}
If that doesn't work for some reason, you can probably use a ValidatorFactoryCustomizer:
#ApplicationScoped
public class MyValidatorFactoryCustomizer implements ValidatorFactoryCustomizer {
#Override
public void customize(BaseHibernateValidatorConfiguration<?> configuration) {
configuration.localeResolver(customlocaleresolver);
}
}
That last solution will, however, completely override any built-in locale resolver: the Accept-Language HTTP header will get completely ignored.
I'm trying to add custom http headers to a webview client (for authorization).
It seems to work in some cases, I'am able to login to a webpage without entering username and password, and I get redirected to another page. But when the page is calling other resources to get elements populated with data an error is thrown and OnReceivedHttpError is invoked. The error I'm getting is 401 unauthorized and when i look through the headers on the IWebResourceRequest i can't see the authorization headers at all.
Am I missing something or have anyone had same problems ?
Using Xamarin Forms 2.3.3.180 and targeting API 21 (Android 5.0 Lollipop), compile with Android 7.1 Nougat.
I've tried in postman to add headers to request and it works perfectly.
Renderer:
public class MyWebViewRenderer : WebViewRenderer
{
private MyWebViewClient _webViewClient;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if(_webViewClient == null)
_webViewClient = new MyWebViewClient();
Control.SetWebViewClient(_webViewClient);
Control.LongClickable = false;
Control.HapticFeedbackEnabled = false;
Control.Settings.JavaScriptEnabled = true;
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
Control.LoadUrl(Control.Url, headers);
}
}
WebViewClient:
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
WebView.SetWebContentsDebuggingEnabled(true);
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
view.LoadUrl(url, headers);
return true;
}
public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest urlResource)
{
//headers does not always contains authorization header, so let's add it.
if (!urlResource.RequestHeaders.ContainsKey("authorization") && !urlResource.RequestHeaders.ContainsKey("Authorization"))
{
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
urlResource.RequestHeaders.Add("Authorization", $"{base64string}");
}
return base.ShouldInterceptRequest(view, urlResource);
}
public override void OnReceivedHttpError(WebView view, IWebResourceRequest request, WebResourceResponse errorResponse)
{
base.OnReceivedHttpError(view, request, errorResponse);
}
If you only need the headers on the get requests, the code below will work. However POST requests are a different issue. I needed to do a similar thing (with all requests, not just GET), and all I can say is that there's not straightforward solution, at least not one that I've found (and I've tried everything short of writing my own network driver). I've tried so many methods (ShouldOverrideUrlLoading, ShouldInterceptRequest, custom LoadUrl and PostUrl etc.) and none of them give a 100% solution. There is a lot of misinformation about this so I think some clarification is needed since I've spent two days on this without success.
So here's what I've learned:
If you only need the headers in the GET requests, that's trivial. Simply create an implementation of WebViewClient and override ShouldOverrideUrlLoading like this:
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(App.Android.HybridWebViewRenderer))]
namespace App.Android
{
public class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.SetWebViewClient(new CustomWebViewClient());
}
}
public class CustomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
{
Dictionary<string, string> headers = new Dictionary<string, string>
{
["Name"] = "value"
};
view.LoadUrl(url, headers);
return true;
}
}
}
If, however, you need the headers in other requests (specifically POST requests) there really isn't a perfect solution. Many answers tell you to override ShouldInterceptRequest but this is unlikely to help. ShouldInterceptRequest provides an IWebResourceRequest which contains the URL of the request, the method (i.e. POST) and the headers. There are answers out there which state that adding the headers by doing request.Headers.Add("Name", "Value") is a viable solution but this is wrong. The IWebResourceRequest is not used by the WebView's internal logic so modifying it is useless!
You can write your own HTTP client in ShouldInterceptRequest which includes your own headers to perform the requests and return a WebResourceResponse object. Again, this works for GET requests, but the problem with this is that even though we can intercept a POST request, we cannot determine the content in the request as the request content is not included in the IWebResourceRequest object. As a result, we cannot accurately perform the request manually. So, unless the content of the POST request is unimportant or can somehow be fetched, this method is not viable.
An additional note on this method: returning null tells the WebView to handle the request for us. In other words 'I don't want to intercept the request'. If the return is not null however, the WebView will display whatever is in the WebResourceResponse object.
I also tried overriding the PostUrl and LoadUrl methods in the WebView itself. These methods are not called by the internal logic, so unless you are calling them yourself, this does not work.
So what can be done? There are a few hacky solutions (see github.com/KeejOow/android-post-webview) to get around this problem, but they rely on javascript and are not suitable in all cases (I have read that they don't work with forms). If you want to use them in Xamarin, you're going to need to adapt the code for C# anyway, and there is no guarantee that it will solve your problem.
I'm writing this so no one else has to waste countless hours finding a solution that doesn't really exist.
If only the Android devs had decided to include the POST content in the IWebResourceRequest object...
And apologies for the length, if you've read to this point, you're probably as desperate as I was.
I'm trying to set some caching HTTP headers for all requests that return an "HTML page".
I could do that in some global place such as BeginRequest or in a global MVC filter (as suggested in that question).
For that to work I must differentiate HTML pages from other requests that are supposed to be cached (mostly resources I think but I'm not sure).
I am unsure how to define "HTML page" in a rigorous way so that I could put it into an algorithm. How could I detect such requests?
I really do not want to mark up all MVC actions that I write individually. That's tedious and I could forget something.
I think the best way is with filters, then I'd use a regex to check if the result has HTML:
Create a filter:
public class NoCacheForHTMLResult : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
Regex tagRegex = new Regex(#"<[^>]+>");
string response = filterContext.RequestContext.HttpContext.Response.Output.ToString();
bool hasHtml = tagRegex.IsMatch(response);
if (hasHtml)
{
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
filterContext.HttpContext.Response.Cache.SetNoStore();
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
...and add in the Global Filters (inside your App_Start):
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new NoCacheForHTMLResult());
}
}
I'm not a regex expert, I got this regex from this question, but it looks we need to improve this, by checking if there is at least an HTML tag, otherwise if the result was XML, it looks the regex would match (though you might want to set no cache for XML as well, it's up to you).
The code for disabling cache I got in this answer, you might have a different code.
Hope this helps you.
I'm trying to reproduce something I found here for a previous version of ASP.NET.
Basically, I want to be able to disable cache so my client's look to the server for information at all times. I've added an HTML meta tag for this, but for client's that already have this information, I wanted to experiment with handling cache policy on the back-end.
The post mentions doing this to set a cache policy as an action filter.
public class NoCacheAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
filterContext.HttpContext.Response.Cache.SetNoStore();
base.OnResultExecuting(filterContext);
}
}
However, HttpContext doesn't appear to have a Response.Cache in ASP.NET Core. Is there an alternative way of doing this?
Thanks!
You could directly set the corresponding response headers to the desired values:
public class NoCacheAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
filterContext.HttpContext.Response.Headers["Expires"] = "-1";
filterContext.HttpContext.Response.Headers["Pragma"] = "no-cache";
base.OnResultExecuting(filterContext);
}
}
You can control it with build-on attribute:
[ResponseCache (NoStore = true, Location = ResponseCacheLocation.None)]
I have a simple Spring MVC RestController
#RestController
#RequestMapping(value = "/xxx")
public class MyResource {
One of the endpoint is meant to return a ZIP file so I have this:
#RequestMapping(value = "/xxx", method = RequestMethod.GET)
public void downloadZip(HttpServletResponse response) {
InputStream is = ...
IOUtils.copy(is, response.getOutputStream());
response.setContentType("application/zip");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, attachment; filename=myfile.zip);
response.flushBuffer();
}
The above is meant to return a ZIP and add two extra header for the browser to know it's something that trigger a download.
But this is not the case, when I check the Response headers with chrome network tools, the header are exactly the same as before the request hit the endpoint.
The content-type is still application/json and the content-disposition is not even there.
I have custom header called "app-version". I did a test an try amending the value to see if it actually gets changed
response.setHeader("app-version","hello");
But no, when I check the Response header the "app-version" is still what was before the call.
Do I have to enable anything to be able to modify header inside my controller ?
Try changing the return type from void to String
public String downloadZip(HttpServletResponse response) {
...
return ""
}
I think spring won't apply any change in header if the endpoint doesn't return anything.