far future Expires header for static contents - playframework-2.1

how wrote here http://developer.yahoo.com/performance/rules.html
For static components: implement "Never expire" policy by setting far future Expires header
i can gain performace avoiding http requests with a response as "304".
In official play! documentation i can see how set cache-controll directives, but how i can set far future Expires header?
best regards Nicola
edit: thanks for the replay now it work as well! here there are the classes:
conf/routes
# Static files
GET /assets/stylesheets/img/:name controllers.StaticFilesController.getBoostrapImg(name)
GET /assets/images/*name controllers.StaticFilesController.getImg(name)
GET /assets/stylesheets/*name controllers.StaticFilesController.getCss(name)
GET /assets/javascripts/*name controllers.StaticFilesController.getJs(name)
controllers/StaticFilesController.java
package controllers;
import org.apache.http.impl.cookie.DateUtils;
import java.util.*;
import play.mvc.*;
import services.FileName;
import play.*;
public class StaticFilesController extends Controller {
private static String nextYearString = StaticFilesController
.getNextYearAsString();
public static Result getImg(String path) {
FileName fileName = new FileName(path);
response().setHeader(EXPIRES, nextYearString);
response().setContentType("image/" + fileName.extension());
return ok(Play.application().getFile("/public/images/" + path));
}
public static Result getBoostrapImg(String path) {
FileName fileName = new FileName(path);
response().setHeader(EXPIRES, nextYearString);
response().setContentType("image/" + fileName.extension());
return ok(Play.application().getFile(
"/public/images/" + fileName.filename() + "."
+ fileName.extension()));
}
public static Result getCss(String path) {
response().setHeader(EXPIRES, nextYearString);
response().setContentType("text/css");
return ok(Play.application().getFile("/public/stylesheets/" + path));
}
public static Result getJs(String path) {
response().setHeader(EXPIRES, nextYearString);
response().setContentType("application/x-javascript");
return ok(Play.application().getFile("/public/javascripts/" + path));
}
private static String getNextYearAsString() {
Calendar calendar = new GregorianCalendar();
calendar.add(Calendar.YEAR, 1);
return DateUtils.formatDate(calendar.getTime());
}
}
services/FileName.java
package services;
/**
* This class assumes that the string used to initialize fullPath has a
* directory path, filename, and extension. The methods won't work if it
* doesn't.
*/
public class FileName {
private String fullPath;
private char pathSeparator, extensionSeparator;
public FileName(String str, char sep, char ext) {
fullPath = str;
pathSeparator = sep;
extensionSeparator = ext;
}
public FileName(String str)
{
fullPath = str;
pathSeparator = '/';
extensionSeparator = '.';
}
public String extension() {
int dot = fullPath.lastIndexOf(extensionSeparator);
return fullPath.substring(dot + 1);
}
public String filename() { // gets filename without extension
int dot = fullPath.lastIndexOf(extensionSeparator);
int sep = fullPath.lastIndexOf(pathSeparator);
return fullPath.substring(sep + 1, dot);
}
public String path() {
int sep = fullPath.lastIndexOf(pathSeparator);
return fullPath.substring(0, sep);
}
}
And the views/main.scala.html
#(skin: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LibreTitan</title>
<link rel="stylesheet" media="screen" href="#routes.StaticFilesController.getCss("bootstrap/bootstrap.min.css")">
#if(skin != null && !skin.equals("")) {
<link rel="stylesheet" media="screen" href="#routes.StaticFilesController.getCss(skin+".min.css")">
}
<link rel="shortcut icon" type="image/png" href="#routes.StaticFilesController.getImg("favicon.png")">
<script async src="#routes.StaticFilesController.getJs("jquery-1.9.0.min.js")"></script>
<script async src="#routes.StaticFilesController.getJs("bootstrap.min.js")"></script>
</head>
<body>
<div class="container">
#content
</div>
</body>
</html>

Never expire policy in this context means that should add Expire header to your response which date is in far future, for an example 10 years since now. You can easily do that in Play as described in the Manipulating the response doc. example:
public static Result index() {
response().setHeader(EXPIRES, "Thu, 16 Feb 2023 20:00:00 GMT");
return ok("<h1>Hello World!</h1>");
}
Of course instead giving expire date as a String you should use some method to calculate it dynamicaly, you can do that with org.joda.time.DateTime (available in Play) and its methods like: plusYears(int years). Most important is that should be formatted finally in RFC 1123 date format.
Edit, of course you can return different kinds of Results - also binary as described in the doc ('Results' section), to check all available options look into API of: play.mvc.Results it can be: ok(Content content) - typical when you render some view, ok(java.io.File content), ok(java.io.InputStream content), etc.
On the other hand...
I'd definitely recommend to DO NOT use Play for serving far future static contents (and any other static, public contents as well). Although you can do it easily, as described above, IMHO it's redundant waste of Play's resources as you need to handle all requests for any single image, script, style... whatever.
Consider using common... HTTP server for that job (nginx, lighthttpd, Apache) or even better some distributed CDN. In that case your app can concern on executing its logic instead of searching the stylesheets on the disk.
P.S. Remember that if you are using Play instead of HTTP server, for adding new static contents which are served from the /public folder you'll need to restart the application, so at least make sure that you are keeping them in some dedicated folder apart of application, so you can add/remove/replace them without stopping the application.

Related

asp-append-version="true" not appending version to javascript file

asp-append-version="true" this should append version to script. With .net core 1.1 it worked just fine. Recently upgraded to version 2.0 and it no longer works. Any ideas why?
https://github.com/MarkPieszak/aspnetcore-angular2-universal/issues/471
Basically you now need
#addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
And to make it easier, you can have a global _ViewImports.cshtml file under the Views folder, and just throw that line in there and it will apply the line to all View pages.
In MVC, we can do versioning by maintaining config value then used public class to append the version in JS and CSS reference.
CS:
public static class StaticFileHelper
{
static string staticVersion;
static StaticFileHelper()
{
staticVersion = System.Configuration.ConfigurationManager.AppSettings["JSVersioning"];
}
public static string StaticFile(this UrlHelper html, string filename)
{
var virtualPath = ReleaseVirtualPath(filename);
var root = html.RequestContext.HttpContext.Request.ApplicationPath;
if (root.Length > 1)
{
virtualPath = root + virtualPath;
}
return virtualPath;
}
}
In case of this I made this solution
<link rel="stylesheet" type="text/css" href="\css\custom.css?v=#Generator.RandomStringGenerator(9)">
So my random generator is generating different strings for version then I never face with this situation again. Also I am sharing random string generator for interested people.
private static Random random = new Random();
public static string RandomStringGenerator(int length)
{
const string chars = "abcdefghijklmnopqrstuwxyz0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

How to minify JavaScript inside script block on view pages

How to minify JavaScript inside a view page's script block with minimal effort?
I have some page specific scripts that would like to put on specific view pages. But the ASP.NET MVC4 bundling and minification only works with script files, not script code inside a view page.
UPDATE
I took Sohnee's advice to extract the scripts into files. But I need to use them on specific pages so what I end up doing is:
on layout page, i created an optional section for page specific javascript block:
#RenderSection("js", required: false)
</body>
then in the view page, let's say Index.cshtml, i render the script section like such:
#section js{
#Scripts.Render("~/bundles/js/" + Path.GetFileNameWithoutExtension(this.VirtualPath))
}
as you can see, it assumes the javascript filename (index.js) is the same as the view page name (index.cshtml). then in the bundle config, i have:
var jsFiles = Directory.GetFiles(HttpContext.Current.Server.MapPath("Scripts/Pages"), "*.js");
foreach (var jsFile in jsFiles)
{
var bundleName = Path.GetFileNameWithoutExtension(jsFile);
bundles.Add(new ScriptBundle("~/bundles/js/" + bundleName).Include(
"~/Scripts/pages/" + Path.GetFileName(jsFile)));
}
then, if you are on index page, the HTML output will be:
<script src="/bundles/js/Index?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>
and if you are on products page, the HTML output will be:
<script src="/bundles/js/Products?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>
You can minify inline scripts using this HTML helper
using Microsoft.Ajax.Utilities;
using System;
namespace System.Web.Mvc
{
public class HtmlHelperExtensions
{
public static MvcHtmlString JsMinify(
this HtmlHelper helper, Func<object, object> markup)
{
string notMinifiedJs =
markup.Invoke(helper.ViewContext)?.ToString() ?? "";
var minifier = new Minifier();
var minifiedJs = minifier.MinifyJavaScript(notMinifiedJs, new CodeSettings
{
EvalTreatment = EvalTreatment.MakeImmediateSafe,
PreserveImportantComments = false
});
return new MvcHtmlString(minifiedJs);
}
}
}
And inside your Razor View use it like this
<script type="text/javascript">
#Html.JsMinify(#<text>
window.Yk = window.Yk || {};
Yk.__load = [];
window.$ = function (f) {
Yk.__load.push(f);
}
</text>)
</script>
If you use System.Web.Optimization than all necessary dlls are already referenced otherwise you can install WebGrease NuGet package.
Some additional details available here: http://www.cleansoft.lv/minify-inline-javascript-in-asp-net-mvc-with-webgrease/
EDIT:
Replaced DynamicInvoke() with Invoke(). No need for runtime checks here, Invoke is much faster than DynamicInvoke. Added .? to check for possible null.
The way to do this with minimal effort is to extract it into a script file. Then you can use bundling and minification just as you want.
If you want to minify it inline, it will be a much greater effort than simply moving the script off-page.
Based on #samfromlv's answer, I created an extension to handle CSS as well. It also takes BundleTable.EnableOptimizations into consideration.
OptimizationExtensions.cs
Adding in an answer for ASP.NET MVC Core. The solution I used to minify inline JS and razor generated html was WebMarkupMin.
It ultimately boiled down to adding these two minuscule changes to my project:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
//added
app.UseWebMarkupMin();
app.UseMvc(.....
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//added
services.AddWebMarkupMin(
options =>
{
//i comment these two lines out after testing locally
options.AllowMinificationInDevelopmentEnvironment = true;
options.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHttpCompression();
}
There's a great blog post by Andrew Lock (author of ASP.NET Core in Action) about using WebMarkupMin https://andrewlock.net/html-minification-using-webmarkupmin-in-asp-net-core/ WebMarkupMin is highly configurable and Andrew's post goes way more indepth, highly recommended reading it intently before just copying and pasting.
A little late for the party, but for .NET Core you could use a TagHelper to minify the content of a script tag like this:
[HtmlTargetElement("script", Attributes = MinifyAttributeName)]
public class ScriptTagHelper : TagHelper
{
private const string MinifyAttributeName = "minify";
[HtmlAttributeName(MinifyAttributeName)]
public bool ShouldMinify { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (!ShouldMinify)
{
await base.ProcessAsync(context, output);
return;
}
var textChildContent = await output.GetChildContentAsync();
var scriptContent = textChildContent.GetContent();
// or use any other minifier here
var minifiedContent = NUglify.Uglify.Js(scriptContent).Code;
output.Content.SetHtmlContent(minifiedContent);
}
}
and then use it in your views:
<script minify="true">
...
</script>
Fenton had a great answer about this: "rather than minify inline JavaScript code, externalize the inline JavaScript code and then you can minify with any standard JavaScript minifiers / bundlers."
Here is how you externalize the JavaScript: https://webdesign.tutsplus.com/tutorials/how-to-externalize-and-minify-javascript--cms-30718
Here is my direct answer to minify the inline JavaScript code (require a bit of manual work).
Copy the inline JavaScript code snippet and paste them into a separate JavaScript file and save it, e.g. inline.js
Use esbuild to minify the inline code snippet in inline.js, see more details about minification here
esbuild --minify < inline.js > inline-minified.js
Copy the minified JavaScript code snippet in inline-minified.js and paste it back into the original HTML to replace the original code inside of the tag.
Done.

Does NancyFX support static content caching via the ETag and Last-Modified headers?

I want my static content (images, javascript files, css files etc) to served in full only after the file has been updated.
If a file has not changed since it was last requested (as determined by the ETag and Last-Modified response header values) then I want the cached versions of the files to be used by the client browser.
Does Nancy support this functionality?
Nancy does partially support the ETag and the Last-Modified headers. It sets them for all static files but as of version 0.13 it does nothing with these values. here is the Nancy code:
Nancy.Responses.GenericFileResponse.cs
if (IsSafeFilePath(rootPath, fullPath))
{
Filename = Path.GetFileName(fullPath);
var fi = new FileInfo(fullPath);
// TODO - set a standard caching time and/or public?
Headers["ETag"] = fi.LastWriteTimeUtc.Ticks.ToString("x");
Headers["Last-Modified"] = fi.LastWriteTimeUtc.ToString("R");
Contents = GetFileContent(fullPath);
ContentType = contentType;
StatusCode = HttpStatusCode.OK;
return;
}
To make use of the ETag and Last-Modified header values you need to add a couple of modified extensions methods. I borrowed these directly from the Nancy source code in GitHub (as this functionality is planned for a future release) but the original idea came from Simon Cropp - Conditional responses with NancyFX
Extension Methods
public static void CheckForIfNonMatch(this NancyContext context)
{
var request = context.Request;
var response = context.Response;
string responseETag;
if (!response.Headers.TryGetValue("ETag", out responseETag)) return;
if (request.Headers.IfNoneMatch.Contains(responseETag))
{
context.Response = HttpStatusCode.NotModified;
}
}
public static void CheckForIfModifiedSince(this NancyContext context)
{
var request = context.Request;
var response = context.Response;
string responseLastModified;
if (!response.Headers.TryGetValue("Last-Modified", out responseLastModified)) return;
DateTime lastModified;
if (!request.Headers.IfModifiedSince.HasValue || !DateTime.TryParseExact(responseLastModified, "R", CultureInfo.InvariantCulture, DateTimeStyles.None, out lastModified)) return;
if (lastModified <= request.Headers.IfModifiedSince.Value)
{
context.Response = HttpStatusCode.NotModified;
}
}
Finally you need to call these methods using the AfterRequest hook in your Nancy BootStrapper.
BootStrapper
public class MyBootstrapper :DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
pipelines.AfterRequest += ctx =>
{
ctx.CheckForIfNoneMatch();
ctx.CheckForIfModifiedSince();
};
base.ApplicationStartup(container, pipelines);
}
//more stuff
}
Watching the responses with Fiddler you will see the first hit to your static files downloads them with a 200 - OK Status Code.
Thereafter each request returns a 304 - Not Modified Status Code. After a file is updated, requesting it once again downloads it with a 200 - OK Status Code ... and so on.

How to deploy ActiveX dll from web page

Been trying off and on for days now and can't figure this out. I have written a C# class file for an Intranet app to control the local serial ports. It works great when I manually register the dll using regasm, however, I need to deploy this control from a web page without having to manually register it. I tried creating a Setup Project in Visual Studio 2010, it compiled fine yet I can not open the object in a webpage.
Here are the pertinent lines of code from my C# class:
namespace wmsSerialPorts
{
[Guid("55D31498-12A5-4FF0-942D-3B0BA449CA7B")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface iAxDevices
{
[DispId(1)]
int OpenPort(string sComPort);
[DispId(2)]
int ClosePort();
[DispId(3)]
int SendCmd(string sCmd);
[DispId(4)]
string GetLastError();
//[DispId(5)]
//string ReadLine();
[DispId(6)]
string ReadWeight();
[DispId(7)]
Microsoft.JScript.ArrayObject GetJsPorts();
[DispId(8)]
void prtLabel(string sItemNum, string sQty, string sDesc, string sWoNum, string sBoxID, string sBoxIDBarCode, string sBoxIDorig);
[DispId(9)]
void prtLabelQC(string sItemNum, string sQty, string sDesc, string sWoNum, string sBoxID, string sBoxIDBarCode, string sBoxIDorig, string sNeedDate, string sRecOverride);
[DispId(10)]
void prtReset();
}
[Guid("E59C5B7E-EF1F-4241-A9FD-191EF8FCC167")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[ProgId("AxDevices")]
public class AXDevices : wmsSerialPorts.SerialCom, iAxDevices, wmsSerialPorts.IObjectSafety
As I mentioned, if I use regasm wmsSerialPorts.dll, the object works great when called from JavaScript like this:
myAx = new ActiveXObject("AXDevices");
My Setup project contains a wmsSerialPorts.inf file:
[version]
signature="$CHICAGO$"
AdvancedINF=2.0
[Setup Hooks]
install=install
[install]
run=msiexec.exe /package """%EXTRACT_DIR%\ActiveXSetup.msi""" /qn
.... and an ActiveXBuild.ddf file:
.Set DiskDirectoryTemplate=cab
.Set CabinetNameTemplate=ActiveXSetup.cab
Debug\ActiveXSetup.msi
wmsSerialPorts.inf
My wmsSerialPorts.dll file is properly referenced as a detached asseembly and building the Setup Project created the ActiveXSetup.cab and ActiveXSetup.msi files as expected.
I then created this HTML page to load the object:
<!DOCTYPE>
<html>
<head>
<title>Test</title>
</head>
<body>
<!-- <object id="AXDevices" classid="clsid:E59C5B7E-EF1F-4241-A9FD-191EF8FCC167" codebase="https://10.0.2.53/BIDWMS/ActiveXSetup.cab">
</object>-->
<object id="AXDevices" classid="clsid:E59C5B7E-EF1F-4241-A9FD-191EF8FCC167" codebase="ActiveXSetup.cab">
</object>
<script type="text/javascript">
try {
var obj = document.AXDevices;
if (obj) {
alert(obj.SayHello());
} else {
alert("Object is not created!");
}
} catch (ex) {
alert("Error message is: " + ex.Description);
}
</script>
</body>
</html>
... but when I run the page, it generates an error of "undefined" (from the catch(ex) block). Any ideas? Thanks in advance ....... Bob
Your Codebase has to be a url, not just a filename. If your file is in C:\inetpub\myCabFiles\ActiveXSetup.cab, and your website is in C:\inetpub, then codebase should be something like
codebase="www.mywebsite.com\myCabFiles\ActiveXSetup.cab"

Presenting file (pdf / tiff / png) content in wicket 1.5

So I've run in to a problem that I can't seem to solve on my own.
I want to present a file in an iFrame. The file can either be pdf, png or tiff, and I don't know which on beforehand.
The pdfs and tiffs should be presented with different actions (printing, saving to disk etc) - I rely on browser plug-ins for this.
What I do is this;
Java:
public ContentPanel(String id, final Atatchment attachment) {
super(id);
ResourceReference rr = new ResourceReference(attachment.getName()) {
private static final long serialVersionUID = 1L;
#Override
public IResource getResource() {
return new ByteArrayResource(attachment.getMimeType(), attachment.getByteArray());
}
};
WebMarkupContainer wmc = new WebMarkupContainer("myIframe");
wmc.add(new AttributeModifier("src", (String) urlFor(rr, null)));
add(wmc);
}
HTML:
<body>
<wicket:panel>
<iframe wicket:id="myIframe" src=""></iframe>
</wicket:panel>
</body>
This results in a 404 ("The requested resource is not availible"). The thing is, when I had some of the files cached they were presented the way I want them to be.
Thanks in advance!
Olle
The problem was the the resource was not registered in the application. Just added:
if (rr.canBeRegistered()) {
getApplication().getResourceReferenceRegistry().registerResourceReference(rr);
}
And it works!