IIS 7.5 forces the http header CacheControl to Private - iis

In my .NET code, I have a custom handler that process Http request and in the ProcessRequest Method calls a custom HttpModule to set Http Cache Headers.
The HttpModule sets the header in the PreSendRequestHeaders method with the following code
HttpCachePolicy cache = response.Cache;
if (cache != null)
{
cache.SetCacheability(HttpCacheability.Public);
cache.SetMaxAge(TimeSpan.FromSeconds(varnishDuration));
response.AppendHeader("Edge-control", String.Concat("!no-store, max-age=", akamaiDuration, "s dca=noop"));
}
In IIS 7.5, with the pool in integrated mode, the CacheControl is forced to private.
Here is what I got :
curl -IXGET -H "Host:myHostName" "http://myServer/mypage"
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Edge-control: !no-store, max-age=300s dca=noop
[...]
I don't understand why IIS changes the CacheControl to private.
Here is my webserver section in my web.config :
<pre>
<system.webServer>
<handlers accessPolicy="Read, Script">
<add name="OxygenHandler" verb="*" path="*" type="com.eurosport.oxygen.server.modules.OxygenHandlerFactory, OxygenServerModules" />
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<add name="WebServiceCachingModule" type="com.eurosport.toolkit.WebServiceCachingModule, Eurosport.Toolkit" />
</modules>
</system.webServer>
</pre>
I tried to add SetSlidingExpiration as mentioned here Cache.SetMaxAge not working under IIS, works fine under VS Dev Srv but it did not help.

I've managed to get it working with the following code in my module :
response.Headers.Remove("Cache-Control");
response.AppendHeader("Cache-Control", "public, max-age=" + varnishDuration.ToString()+", s-max-age=" + varnishDuration.ToString());
It looks dirty but it seems that response.CacheControl and response.Cache properties are not used by IIS in integrated mode (or are overriden by some module...)

The default is specified in System.Web.HttpResponse.CacheControl:
/// <devdoc>
/// <para>
/// Provided for ASP compatiblility. Use the <see cref='System.Web.HttpResponse.Cache'/>
/// property instead.
/// </para>
/// </devdoc>
public string CacheControl {
get {
if (_cacheControl == null) {
// the default
return "private";
}
return _cacheControl;
}
While you can override the header through (global) filters, this doesn't work for error pages caused by authentication/authorization. Luckily there's a nice entry point for each request, allowing you to override this default:
// In Global.asax.cs:
protected void Application_BeginRequest()
{
Context.Response.CacheControl = "no-cache";
}
Update: Setting cache-control per above will disable caching of bundles. I'm now using the following workaround. It only changes the page's cacheability when it was not explicitly set. The default value of '6' comes from here:
// In Global.asax.cs:
protected void Application_EndRequest()
{
if ((int)Response.Cache.GetCacheability() == ((int)HttpCacheability.ServerAndPrivate) + 1)
Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
Furthermore when there's an error and the YSOD (yellow error page) is rendered through ReportRuntimeError, the framework will call ClearHeaders and your custom cache-control setting will be overridden. I haven't found a solution for this.

Related

Can I force Azure API Management to return 400 instead of 404 when missing required field?

We have an application that requires some fields to be present. If those fields aren't present we will return a 400 response explaining what is missing in a proper error message. Adding APIM to the mix complicates it a lot it seems. Since APIM know that the field is required it looks like it will short curcuit and return 404 with a generic message instead of our self explanatory message of what is wrong.
Is it a way to turn of this functionality for APIM?
I'm getting the same issue and I endup changing my approach. What I did was to configure it on the Application side and use FluentValidation to make the querystring parameters required. So, my model now looks something like this:
using FluentValidation;
public class UrlQueryParameters
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
public class UrlQueryParametersValidator : AbstractValidator<UrlQueryParameters>
{
public UrlQueryParametersValidator()
{
RuleFor(o => o.PropertyA)
.NotEmpty()
.WithMessage("The 'PropertyA' parameter was missing or a value was not provided.");
RuleFor(o => o.PropertyB)
.NotEmpty()
.WithMessage("The 'PropertyB' parameter was missing or a value was not provided.");
}
}
The preceding code defines a couple of validation rules with custom messages for PropertyA and PropertyB properties.
Now, enable FluentValidation as the default validation mechanism for our application by adding the following code at ConfigureServices method of Startup.cs file:
public void ConfigureServices(IServiceCollection services) {
// Rest of the code omitted for brevity
services
.AddControllers()
.AddFluentValidation(fv =>
{
fv.DisableDataAnnotationsValidation = true;
// The following line registers ALL public validators in the assembly containing UrlQueryParametersValidator
// There is no need to register additional validators from this assembly.
fv.RegisterValidatorsFromAssemblyContaining<UrlQueryParametersValidator>(lifetime: ServiceLifetime.Singleton);
});
}
At this point, your API endpoints should validate the required parameters from the request and APIM should not short-circuit the request by throwing 404 Not Found when you try to access /api/foo/{id}.
The reason why this works because Swashbuckle doesn't automatically import validation rules from FluentValidation. Meaning, the properties PropertyA and PropertyB won't be marked as required when viewing them in the Swagger UI. This is the downside for this approach as the required querystring parameters from the Swagger UI will not be marked as required which could be confusing to consumers. But to me, returning the correct StatusCode with meaningful message to consumers is more important and that's why I will stick to this workaround for the time being. You could try using the MicroElements.Swashbuckle.FluentValidation to altleast set/marked the parameters as required in the Swagger UI schema. But that's just about it.
I blogged about this it here: Dirty Hack on Making the Required QueryString Params to Work in Azure APIM
At API/Product/Global level policy add on-error section, use choose policy to check if operation was found or not:
<choose>
<when condition="#(context.LastError.Source == "configuration" && context.LastError.Reason == "OperationNotFound")">
<return-response>
<set-status code="400" reason="Bad Request" />
</return-response>
</when>
</choose>

Umbraco Preview not showing the correct content

When I have the following key in my web.config, umbraco preview is working fine, showing me the correct content but I can not login to my web site, I can login to umbraco backoffice without any problem.
When I comment out the following key, then I can login to my web site but this time umbraco preview is not showing the correct content.
What should I do to make preview work and at the same time I can login to my front end site? I have already searched for a solution, but I haven't managed to fix this so far. Any help is appreciated.
Umbraco version 7.4.3 assembly: 1.0.5948.18141
The key:
<add key="owin:appStartup" value="UmbracoDefaultOwinStartup" />
My OwinStartup class:
[assembly: OwinStartup(typeof(OwinStartup), "Configuration")]
namespace ABC.XYZ.Site
{
public class OwinStartup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
base.Configuration(app);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions());
app.Use((context, next) =>
{
var loggedInMarkerCookie = context.Request.Cookies[Settings.CookieLoggedInMarkerName];
var autoLoginAttempt = context.Request.Cookies[Settings.CookieLoggedInMarkerAttemptName];
if (!context.Authentication.User.Identity.IsAuthenticated && !context.Request.Path.Value.StartsWith("/sso/") && (loggedInMarkerCookie != null && autoLoginAttempt == null))
{
context.Response.Cookies.Append(Settings.CookieLoggedInMarkerAttemptName, DateTime.Now.ToString(), new CookieOptions { Expires = DateTime.Now.AddMinutes(30) });
context.Authentication.Challenge();
}
return next.Invoke();
});
}
}
}
Your App Setting needs to be:
<add key="owin:appStartup" value="ABC.XYZ.Site, OwinStartup" />
And looking at the source I think that you need to call base.Configuration(app); after you've added your custom configuration.

Web Api max parameter length

I have a very simple dto
public class RisRequest
{
public string APIKey { get; set; }
public string Message { get; set; }
}
And a nice easy web api
[HttpPost]
public HttpResponseMessage rad(RisRequest request)
99% of the time this works. but when I hit a request where the Message is longer than the norm (>100,000 characters, and 112kb) then this parameter is null. note the APIKey is still good, my RisRequest object isn't null, but only th eMessage parameter.
I did some googling and tried a bunch of options
as per This link I tried setting the httpconfig buffer
config.Formatters.FormUrlEncodedFormatter.ReadBufferSize = int.MaxValue/10;
I tried the web.config options in this link
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering>
</security>
</system.webServer>
and
<system.web>
<httpRuntime maxRequestLength="2097152" />
</system.web>
and no luck with either. all other some suggest a variation of the same.
Any ideas where I am going wrong?
Thank
According to Max Parameter length in MVC
this is a windows restriction. in your url, the parameter is part of the path. windows restricts a path segments length.
you should change UrlSegmentMaxLength in regedit.
create a DWORD value in the following registery key
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters
UrlSegmentMaxCount
Maximum number of URL path segments. If zero, the count bounded by the
maximum value of a ULONG.
Valid value range 0 - 16,383

MVC 5 Forms authentication retuns null for User.Identity.Name

Authentication fails to authenticate for forms authentication in my MVC 5 application. Page gets redirected correctly, but User.Identity.IsAuthenticated and User.Identity.Name values are empty.
My webconfig,
<system.web>
<authentication mode="Forms">
<forms cookieless="UseCookies" defaultUrl="~/" loginUrl="~/user/signin" name="MYAPPWeb" timeout="21600" slidingExpiration="true"/>
</authentication>
UserController,
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult SignIn(SignInViewModel user)
{
UserDTO userObj;
using (var services = new ServiceFactory())
{
userObj = services.UserManagement.ValidateLoginDetails(ConfigHelper.EnvironmentString, user.TenantName, user.Username, user.Password);
}
string userID = userObj.UserID.ToString();
//FormsAuthentication.RedirectFromLoginPage(userID, user.RememberMe);
FormsAuthentication.SetAuthCookie(userID.ToString(),true);
FormsAuthentication.RedirectFromLoginPage(userID, false); //DO NOT REMEMBER ME
}
HomeController (Default page)
public ActionResult Index()
{
bool x = User.Identity.IsAuthenticated; //false?
string y = User.Identity.Name; //null?
return View();
}
It looks pretty straight forward, am I missing something? Please help!
Note:
When I create the project I selected windows authentication. It created some Owin authenticaiton related configuration cs files (startup.auth.cs). I have removed them and added the above appsetting entry as it is required to stop loading Owin assemblies.
<add key="owin:AutomaticAppStartup" value="false"/>
If your project has Owin authentication by default, it will remove form authentication from the project.
If you see your web config you may see
<remove name="FormsAuthentication" />
configuration.
Simply remove it.
I had the same problem and it solved the issue.

Can anyone give me example how to create new IBM Connections Activity using xPages Social Enabler?

Can anyone give me an example how to create new IBM Connections Activity using xPages Social Enabler? I cant find any usefull info in documentation so I have adapted an example from Niklas Heidloff on how to create a new bookmark in Connections. I have the following code for creating a new activity:
try {
var svc = new sbt.ConnectionsService("/activities/service/atom2/activities");
var sb = new java.lang.StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<entry xmlns:snx=\"http://www.ibm.com/xmlns/prod/sn\" xmlns:opensearch=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:thr=\"http://purl.org/syndication/thread/1.0\" xmlns=\"http://www.w3.org/2005/Atom\">");
sb.append("<title type=\"text\">");
sb.append("test activity from xpages");
sb.append("</title>");
sb.append("<content type=\"html\">");
sb.append("</content>");
sb.append("</entry>");
var msg = svc.post(null, sb.toString(), "xml");
} catch(e) {
print(e)
}
But code above do not create anything but raises error on Domino console. This is returned by svc.post() command:
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: Client service request to: http://vhost1279.site1.compute.ihost.com:81/activities/service/atom2/activities did not return OK status. Status returned: 415, reason: Unsupported Media Type, expected:information, please consult error-l
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: g-0.xml located in /local/opt/ibm/lotus/notesdata/domino/workspace/logs
[31726:00075-3041917840] 11/19/2012 01:03:59 PM HTTP JVM: com.ibm.xsp.extlib.sbt.services.client.ClientServicesException: HTTP Status 415, Unsupported Media Type. HTTP error response code received in response to request to url: http://vhost1279.site1.comties/service/atom2/activities
Can anyone give me a hint how to use it properly or point me to some usefull documentation?
Don't use a StringBuilder to create XML. At least use SAX or better Apache Abdera for creating XML (Tutorial here). This ensures that your XML is valid and in case of Abdera also valid ATOM.
It is important to use this approach, since you get a Node object in return, that automatically triggers the needed content type.
Then check how to create an Activity in the Connections documentation wiki (yes - confusing). In this article you find the code to retrieve an activity - I actually recommend to use CURL to get the valid format as an example. Some CURL URLs are here. The closest you get to a full example is Luis' demo of a status update.
To explore connections I use the following batch file:
set server=[server]
set HOME=c:\work
curl %server%%1 –-netrc -G --basic -k -v -L -o %2 %3 %4 %5 %6 %7
with an .netrc file (see the CURL documentation)
machine [server] login [user] password [password]
This is the XML format you need for an activity:
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="activity" label="Activity"/>
<title type="text">Posted activity</title>
<content type="html">
This is an activity that has been automatically uploaded from the cURL command line
</content>
</entry>
And post it like this:
post activities/service/atom2/activities newactivity.xml activityresult.xml
Open the activityresult.xml and locate ocate the href attribute of the app:collection element - you need it to add actions. Use the following XML:
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:snx="http://www.ibm.com/xmlns/prod/sn">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="todo"/>
<category term="Connection4.0"/>
<category term="Test"/>
<title type="text">Some things that need to be done</title>
<content type="html">
This is an <b>action</b> in an activity that has been automatically uploaded from the cURL command line.
</content>
<snx:assignedto>noreply#ibm.com</snx:assignedto>
</entry>
and this command:
post [the-url-you-found-above] newaction.xml actionresult.xml
Once the CURL version works you can try yourself using Abdera code.
Here is a working sample from REST client in Firefox:
https://vhost1279.site1.compute.ihost.com/activities/service/atom2/activities
Header: Content-Type application/atom+xml
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:snx="http://www.ibm.com/xmlns/prod/sn" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns="http://www.w3.org/2005/Atom">
<category scheme="http://www.ibm.com/xmlns/prod/sn/type" term="activity" label="Activity" />
<content type="html"/>
<title type="text">
test
</title>
</entry>
The problem with the code above is that you pass in a String to the post method. That however doesn't set the right content type. Please use APIs Stephan suggests to create a org.w3c.dom.Node with the XML and pass this in instead. This will set automatically the right content type in the header.
SOLVED!!! I looked to source and problem was obvious. This is rather a bug or at least misconception but can be easily resolved . According the docs and my testing proves this Connections requires the following header in request: Content-Type = application/atom+xml .... but in source code of Social Enabler I found these two related methods:
protected void prepareRequest(HttpClient httpClient, HttpRequestBase httpRequestBase, Options options) throws ClientServicesException {
// TODO: add support for gzip content
//httpClient.addRequestHeader("Accept-Encoding", "gzip");
if(options.getHeaders()!=null) {
addHeaders(httpClient, httpRequestBase, options);
}
if (options.content != null) {
String contentType = null;
HttpEntity entity = null;
Object content = options.content;
try {
//If a subclass overrides com.ibm.xsp.extlib.services.client.Service.processRequestContent(HttpRequestBase, Object, Options)
//the the subclass must set the content type of the request, and also set the request's entity!
if(processRequestContent(httpClient, httpRequestBase, options)){
if (content instanceof IValue) {
JsonFactory jsFactory = new JsonJavaScriptFactory(DesignerRuntime.getJSContext());
entity = new StringEntity(JsonGenerator.toJson(jsFactory, content, true));
contentType = "application/json";
}
else if (content instanceof JsonObject) {
JsonFactory jsFactory = JsonJavaFactory.instanceEx;
entity = new StringEntity(JsonGenerator.toJson(jsFactory, content, true));
contentType = "application/json";
}
else if (content instanceof Node) {
entity = new StringEntity(DOMUtil.getXMLString((Node) content, true));
contentType = "application/xml";
}
else {
entity = new StringEntity(content.toString());
contentType = findRequestTextContentType(options);
}
}
} catch (Exception ex) {
if(ex instanceof ClientServicesException) {
throw (ClientServicesException)ex;
}
throw new ClientServicesException(ex, "Error while parsing request content");
}
if (entity != null && (httpRequestBase instanceof HttpEntityEnclosingRequestBase)) {
httpRequestBase.setHeader("Content-type", contentType);
((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(entity);
}
}
}
protected String findRequestTextContentType(Options options) {
return "text/plain";
}
As you can see there is no such header (application/atom+xml) for any case. But if you provide XML content as string, the code uses the 'findRequestTextContentType' method to return a default content type, which is 'text/plain' that is not correct for our situation. It is hardcoded so there is no way how to setup the default encoding. But, at least, the 'findRequestTextContentType' is type protected so it can be overriden. So I have created my own ConnectionsService class that extends the former one and overrides the findRequestTextContentType method to return correct content type for my case. And this works fine and resolved the problem !!
import sbt.ConnectionsService;
public class ConnectionsServiceCustom extends ConnectionsService {
public ConnectionsServiceTcl(String serviceUrl) {
super(serviceUrl);
// TODO Auto-generated constructor stub
}
#Override
protected String findRequestTextContentType(Options options) {
return "application/atom+xml";
}
}
As Niklas and Stephen point out you need to use a Dom object (Node, Document etc).. If you are getting an error when creating such an object then it is most likely because the contents of the document/node is poorly formatted or incorrect..
There is a built in XPages util class that allows you to create documents from strings
com.ibm.commons.xml.DOMUtil
Check out
com.ibm.commons.xml.DOMUtil.createDocument(String, String)
e.g.
com.ibm.commons.xml.DOMUtil.createDocument("my xml string", null);
The first parameter is the contents of the XML document, the second is the format.
This class provides several utility methods for parsing and constructing DOM documents.

Resources