AnjLab Blog
В этом разделе транслируются сообщения, которые участники команды размещают на своих персональных блогах.
How To Determine Client TimeZone In A Web Application
There are several ways to determine client timezone.
One of them is resolving client IP address to location:
Every web framework provides API to get client IP. For instance, in java there is a method ServletRequest.getRemoteAddr() for this purpose.
To resolve IP and location information you can use one of the numerous web services available online.
For instance, to resolve IP to location Ping Service uses IP-whois.net service.
Another service, Geonames.org provides web service API to get timezone information by latitude/longitude pair.
Here's an implementation of described approach in java:
private TimeZone getTimeZoneByClientIP() {
TimeZone timeZone = UTC_TIME_ZONE;
try {
String clientIP = globals.getHTTPServletRequest().getRemoteAddr();
if (!Utils.isNullOrEmpty(clientIP)) {
Location location = locationResolver.resolveLocation(clientIP);
if (!location.isEmpty()) {
timeZone = timeZoneResolver.resolveTimeZone(location.getLatitude(), location.getLongitude());
}
if (timeZone == null) {
timeZone = UTC_TIME_ZONE;
}
}
logger.debug("Resolved timeZoneId is {}", timeZone.getID());
} catch (Exception e) {
logger.error("Error resolving client timezone by ip "
+ globals.getHTTPServletRequest().getRemoteAddr(), e);
}
return timeZone;
}
The disadvantages using this approach are:
Note: according to Ping Service statistics IP-Whois.net availability is close to 100% with average response time ~270 ms, while Geonames.org availability is only around 80% with average response time ~1100 ms. Geonames.org low level availability is due to GAE hosting: Geonames.org restricts free access to its API to 3000 requests per IP per hour.
On the other hand you have really simple solution to implement that allows to determine client timezone at the very first client request so you can display all date/time sensitive data using client local time.
See also:
Update:
GAE 1.6.5 introduces some request headers which already contains Lat/Lng pair for incoming request: https://developers.google.com/appengine/docs/java/runtime#Request_Headers
Read more [Dmitry Gusev Blog]
Sharepoint: problem with querying list by indexed columns
If you have a Sharepoint list with > 5000 items you can get problems querying list. Especially if you query by indexed columns. The query will always return 0 records. As I noticed such problem appears on Lookup and Number(Integer) columns. To fix it try to change value of 'Type' attribute to 'Integer' instead of 'Lookup' or 'Number'. P.S. I think the problem is in indexed columns. Lookup fields are indexed as Integer values in Sharepoint index tables
Read more [Nikolay Zhebrun Blog]
Serving Tapestry5 Assets As Static Resources
During the render phase Tapestry5 converts the ${context:/css/all.css} part to asset URL, which may look like the following (see Asset URLs section here):
Here "stage-20120310" -- is an application version string, which Tapestry5 adds to asset URLs to manage assets versioning. When running in production Tapestry5 adds a far future expires header for the asset, which will encourage the client browser to cache it.
When you change one of your assets you have to change application version number in your AppModule.java, so that Tapestry5 generate new asset URLs and browser fetched new assets instead of using the ones from cache.
One disadvantage of such approach is that client browser will have to get all the assets once again, not just the one that was changed.
For the majority of assets the asset URL is generated by Tapestry5. Exceptions are assets, that are referenced from *.css files by the relative URL, like this (file all.css):
In this case browser will form the URL itself relatively to "/assets/stage-20120310/ctx/css/all.css", and the resulting URL will be "/assets/stage-20120310/ctx/images/external.png".
So you have to change application version in AppModule.java if you provide new version of "external.png".
But, for the majority of assets it would be enough to append MD5/SHA1/... checksum as a GET-parameter to asset URL and make them look like:
There are several ways this may be implemented. Ideally, I'd like this to be implemented in Tapestry5 core.
There's one thing I don't like about Tapestry5 assets handling, though, even if the above solution will be implemented -- is that assets are not static.
This means every asset URL is handled by the Java code, and in most cases assets handling is just streaming of existing files from filesystem to browser (with optional minimization and gzip-compression).
Once the asset was handled, Tapestry5 caches the response and uses it in further responses, but still this is all done in Java.
In Ping Service we've implemented "assets precompilation", and placed all the rendered assets as static files in the web app root folder.
This is done using custom implementation of org.apache.tapestry5.internal.services.ResourceStreamer, which is responsible for streaming every asset to client. During resource streaming we calculate asset checksum and store in a static.properties file, where we put asset URL as a key, and checksum as a value:
Our implementation of AssetPathConverter uses this property file to modify asset URLs.
Also we've implemented it to work only if special HTTP-header passed with the request. To pass this header and to trigger every asset we have in our application, we use Selenium-powered integration test that queries every single page. We run this test before deploying new version to production.
Now Tapestry5 asset URLs and URLs of static files are the same in our application. So Google App Engine runtime won't even pass the request to Java. Also it uses its own facilities to serve static files, i.e. gzip-compression, etc.
Read more [Dmitry Gusev Blog]
