Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
content-disposition: attachment; filename=Hill%20Country%20water%20issues[1].ppt
Content-Type: application/unknown
IIS servers don't come provisioned with the .ppt extension mapped to application/vnd.ms-powerpoint? Microsoft server? Microsoft application?
What hope can there be for authoritative metadata?
My Firefox browser figures it out just fine, and launches Open Office. Presumably FF went through all this first, just to determine it to be application/octet-stream; then ran a further sniffer to identify it as a Power Point.
Hugh Winkler holding forth on computing and the Web
Friday, November 16, 2007
Tuesday, November 06, 2007
It's the hyperlinks, Stupid!
Henry Story thinks Echo2 is "Web 2.0 in Java", and they do have a killer demo. But it's yet another example of incredibly brilliant developers going to great lengths to bring the desktop app to the browser, and ignoring that the value of the web lies in hyperlinks.
So navigate that demo: Click the "Next" arrow. Your address bar doesn't change even though you have navigated to what most people would call a clearly identifiable different resource.
I can't give you the link to "page 2" because it isn't addressable on the web. Sorry. Go to page 1, then click to go to page 2.
Under the hood, it's the usual collection of RPCs masquerading as URLs -- some GET, some POST -- using port 80 since we know it's open, after all. Every single GET uses all the usual tricks to make sure that nothing -- not even JPEGs -- gets cached.
(Why don't we just give Javascript in browsers an API to open up a socket and execute any protocol you like? Seriously: Wouldn't that be better than abusing HTTP? If you don't want to use the HTTP protocol, you shouldn't have to.)
I love the look of that demo, and I think their technology is clever. With a little effort I am sure they can make the framework webby.
So navigate that demo: Click the "Next" arrow. Your address bar doesn't change even though you have navigated to what most people would call a clearly identifiable different resource.
I can't give you the link to "page 2" because it isn't addressable on the web. Sorry. Go to page 1, then click to go to page 2.
Under the hood, it's the usual collection of RPCs masquerading as URLs -- some GET, some POST -- using port 80 since we know it's open, after all. Every single GET uses all the usual tricks to make sure that nothing -- not even JPEGs -- gets cached.
(Why don't we just give Javascript in browsers an API to open up a socket and execute any protocol you like? Seriously: Wouldn't that be better than abusing HTTP? If you don't want to use the HTTP protocol, you shouldn't have to.)
I love the look of that demo, and I think their technology is clever. With a little effort I am sure they can make the framework webby.
Saturday, November 03, 2007
I was just thinking that!
I've been working on the technology requirements for a "rich web application". All the pieces are in place for a cross platform solution. With Javascript, HTML, SVG, and CSS, you can build a pretty rich application without resorting to JavaFX, AIR, Flex, Silverlight, Click Once, or Web Start.
All the browsers support JS, HTML, SVG and CSS. Except one. Per Rob Sayre:
Why should we, as a a software company, invest in technology from companies that are actively working to subvert what we want to do?
All the browsers support JS, HTML, SVG and CSS. Except one. Per Rob Sayre:
If Microsoft were really interested in making life easier for web developers, they could do so, without a standards committee. They would need to fix the (nasty) bugs in IE’s JScript engine, implement SVG, implement canvas, implement more of CSS, support a standard event model, and on and on. Then, the behavior of IE would be a lot closer to Firefox, Opera, and Safari.It's no secret Microsoft doesn't see an advantage in a web built on cross platform technologies. I'm not very sure about Adobe, either.
Why should we, as a a software company, invest in technology from companies that are actively working to subvert what we want to do?
Friday, October 26, 2007
Rich Web Applications
Here's the five step test to determine whether your "rich internet application" is a rich web application:
If saving isn't a feature of your app, fine: But honor (1) ,(2), and (3).
You can do this with .Net Click Once, Java Web Start, and for all I know, Adobe AIR. But the design of the first two, at least originally, amounted to a way to install and launch your application by clicking a hyperlink, and update the code automatically. Worthy objectives. But no actual Web Start or Click Once apps I have used can do this: Click a document, edit it, save on server. You couldn't possibly do it with Web Start until JDK 1.5. And to do it with Click Once you have to sign the application and party in the registry.
The first thing is to register your RWA as the helper application for your document's mime type and file extension. Web Start enabled doing so when they added <association> to the JNLP 1.5 format. Web Start registers itself as the handler for your file extension; the browser launches javaws to open your file, and javaws looks at the file extension and invokes your application. Or: sign your Click Once application and you can set your application as the mime type handler in the registry.
That makes your application launchable when you click a link.
You will have to design the URL of the document into the document itself. Browsers download documents and save them to temp files; they don't tell you the URL of the document. When the browser launches your helper app, your app needs to know where to HTTP PUT updates, or to DELETE the resource. You can also design in other URLS your client understands: a base URL under which you can POST to implement "File/New", for example. Notice Atom has <link rel="self"> and Atompub service documents tell you where to POST things; you could build a good RWA around that scaffolding. Make your own documents describe how to navigate the states of your application by traversing hyperlinks.
Your RWA should have an address bar. Show the users where they are. Let them type in any URL they want to. If they type in one for a mime-type you don't understand, hand off the download to the registered helper application, or back to the browser.
All those buttons desktop apps use to navigate? Menu commands? Keyboard shortcuts? Back them with URLs you GET from or POST to. Display each URL in the address bar as the user traverses your application. After clicking any button that does GET, whatever you see in your RWA should be what your friend will see when you email him the URL in the address bar.
Automatic code update (step 2) is a necessary feature for a RWA. It's not enough just to have a link launchable application. You can evolve your schema, and change the meanings of elements in your document format, as long as you know that only the most modern code, that understands the new meanings, will be interpreting the document.
Make the extra effort to leverage the web architecture, and I won't blog about your beautiful but shitty application.
- You click on a hyperlink in a web browser.
- Your RWA opens up, and updates its code if necessary
- Your RWA renders the document, and you edit it.
- You save the file -- to its URL
- You e-mail the hyperlink to someone; they open up the document and see your edits.
If saving isn't a feature of your app, fine: But honor (1) ,(2), and (3).
You can do this with .Net Click Once, Java Web Start, and for all I know, Adobe AIR. But the design of the first two, at least originally, amounted to a way to install and launch your application by clicking a hyperlink, and update the code automatically. Worthy objectives. But no actual Web Start or Click Once apps I have used can do this: Click a document, edit it, save on server. You couldn't possibly do it with Web Start until JDK 1.5. And to do it with Click Once you have to sign the application and party in the registry.
The first thing is to register your RWA as the helper application for your document's mime type and file extension. Web Start enabled doing so when they added <association> to the JNLP 1.5 format. Web Start registers itself as the handler for your file extension; the browser launches javaws to open your file, and javaws looks at the file extension and invokes your application. Or: sign your Click Once application and you can set your application as the mime type handler in the registry.
That makes your application launchable when you click a link.
You will have to design the URL of the document into the document itself. Browsers download documents and save them to temp files; they don't tell you the URL of the document. When the browser launches your helper app, your app needs to know where to HTTP PUT updates, or to DELETE the resource. You can also design in other URLS your client understands: a base URL under which you can POST to implement "File/New", for example. Notice Atom has <link rel="self"> and Atompub service documents tell you where to POST things; you could build a good RWA around that scaffolding. Make your own documents describe how to navigate the states of your application by traversing hyperlinks.
Your RWA should have an address bar. Show the users where they are. Let them type in any URL they want to. If they type in one for a mime-type you don't understand, hand off the download to the registered helper application, or back to the browser.
All those buttons desktop apps use to navigate? Menu commands? Keyboard shortcuts? Back them with URLs you GET from or POST to. Display each URL in the address bar as the user traverses your application. After clicking any button that does GET, whatever you see in your RWA should be what your friend will see when you email him the URL in the address bar.
Automatic code update (step 2) is a necessary feature for a RWA. It's not enough just to have a link launchable application. You can evolve your schema, and change the meanings of elements in your document format, as long as you know that only the most modern code, that understands the new meanings, will be interpreting the document.
Make the extra effort to leverage the web architecture, and I won't blog about your beautiful but shitty application.
Tuesday, October 02, 2007
That killer web platform
Here we go again with this specious argument that the Web isn't rich enough. Joel thinks there's a new killer platform out there to be invented, that will seize control of web applications as Windows seized the desktop.
Ain't gonna happen. Or, if you prefer: Already happened.
Right there in his own essay is the reason.
See, Ajax gives you the capability to turn a perfectly good hypertext application into a miserable facsimile of a 1980's PC. And you're not going to fix Ajax by adding a bunch of new APIs. Applications need more constraints, not fewer.
Think how absurd it is that you can't copy a picture from GMail to Flickr. The tools are right there, but the application designers do not leverage them. a) Right click photo in GMail. b) "Copy link location". c) Paste hyperlink into Flickr. d) Flickr either downloads photo from GMail or references it. No new APIs needed -- it's all just hyperlinks.
It's great, and necessary, to extend HTML with rich widgets. We'll never capture them all, declaratively, in a common HTML. I am, even as we speak, constructing a Flash widget. But the web is the platform. Any time I push information deep into my widget -- text that could be searchable, graphics that could be linkable -- and hide it from the web, I've failed to leverage the platform.
Ain't gonna happen. Or, if you prefer: Already happened.
Right there in his own essay is the reason.
And that’s exactly where we are with Ajax development today. Sure, yeah, the usability is much better than the first generation DOS apps, because we’ve learned some things since then. But Ajax apps can be inconsistent, and have a lot of trouble working together — you can’t really cut and paste objects from one Ajax app to another, for example, so I’m not sure how you get a picture from Gmail to Flickr. Come on guys, Cut and Paste was invented 25 years ago.
See, Ajax gives you the capability to turn a perfectly good hypertext application into a miserable facsimile of a 1980's PC. And you're not going to fix Ajax by adding a bunch of new APIs. Applications need more constraints, not fewer.
Think how absurd it is that you can't copy a picture from GMail to Flickr. The tools are right there, but the application designers do not leverage them. a) Right click photo in GMail. b) "Copy link location". c) Paste hyperlink into Flickr. d) Flickr either downloads photo from GMail or references it. No new APIs needed -- it's all just hyperlinks.
It's great, and necessary, to extend HTML with rich widgets. We'll never capture them all, declaratively, in a common HTML. I am, even as we speak, constructing a Flash widget. But the web is the platform. Any time I push information deep into my widget -- text that could be searchable, graphics that could be linkable -- and hide it from the web, I've failed to leverage the platform.
Thursday, September 13, 2007
Oh crap, I just invented Prolog
Following a link trail that started with a discussion of CouchDB, I just found this old comment posted by Bill de hÓra:
I don't even want to design database schemas. To hell with modeling. I just want a system that takes a great, undifferentiated pile of facts, and infers entities based on the statistics, primarily of the content, but also on the access patterns. Actually I don't even care that it infer entities; that's an implementation detail I don't need to know about. It would just optimize for query time, or insert time, or some combination; inferring some table structure will probably help it do that.
Why do I need to tell the computer the relations in my data? All I should have to do is insert facts, as triples. When I ask it a question, I want those facts returned (or maybe other conclusions).
Is a really tweaked Prolog the ultimate DBMS?
Perhaps we can go top down - write smart analysers to dynamically denorm data based on usage patterns; indeed database optimisation is an industry sector. But another, dumber, option is bottom up - avoid the initial structural 'typing' step and normalise where necessary....Normalisation can be done later on, based on demand.
I don't even want to design database schemas. To hell with modeling. I just want a system that takes a great, undifferentiated pile of facts, and infers entities based on the statistics, primarily of the content, but also on the access patterns. Actually I don't even care that it infer entities; that's an implementation detail I don't need to know about. It would just optimize for query time, or insert time, or some combination; inferring some table structure will probably help it do that.
Why do I need to tell the computer the relations in my data? All I should have to do is insert facts, as triples. When I ask it a question, I want those facts returned (or maybe other conclusions).
Is a really tweaked Prolog the ultimate DBMS?
Sunday, September 09, 2007
Using Atom to manage lists of Atom feeds
As Phil Wilson does, I have come to rely on Planet Intertwingly as a substitute, largely, for maintaining my own list of interesting feeds. But now Phil wants to filter Sam's picks; he wants to select favorites from the PI OPML.
That raises the interesting prospect that we now need Atom feeds for OPML updates. When Sam adds a new feed, I would want to check it out. Since OPML content is, by definition, a list, I guess Sam could mark up the feed with Simple List Extensions. I'm temporarily suspending my suspicion of SLE, because PI's growing and shrinking list of feeds is clearly unlike an ordinary, infinitely growing feed.
Using Atom to manage lists of Atom feeds demonstrates that Atom captures a powerful abstraction.
That raises the interesting prospect that we now need Atom feeds for OPML updates. When Sam adds a new feed, I would want to check it out. Since OPML content is, by definition, a list, I guess Sam could mark up the feed with Simple List Extensions. I'm temporarily suspending my suspicion of SLE, because PI's growing and shrinking list of feeds is clearly unlike an ordinary, infinitely growing feed.
Using Atom to manage lists of Atom feeds demonstrates that Atom captures a powerful abstraction.
Wednesday, August 15, 2007
Acura ITX
A year and a half ago, I started following this (anonymous?) guy's blog about his project to install an ITX computer in his Acura. It covers everything: selecting hardware including touch screen and GPS, configuring software (Windows + misc. media toys), installation, and he's recently updated it with new photos.
I'd do things a little differently: Carbuntu (hey... carbuntu.org seems to have disappeared...), and I'd want a HSDPA or EVDO card and a dual core Intel... but there's little doubt mine would come out a mess. This fellow's really put in the polish.
I'd do things a little differently: Carbuntu (hey... carbuntu.org seems to have disappeared...), and I'd want a HSDPA or EVDO card and a dual core Intel... but there's little doubt mine would come out a mess. This fellow's really put in the polish.
Restful Servlets + JSP: My framework
Stefan and Bill asked, so here's how I do it (with a tip of the hat to Django).
It is a micro framework. You subclass RestServlet and declare some URL patterns to match, and handlers for them. The base class parses the URI, sets attributes in the ServletRequest object based on the URI pattern, and invokes your handlers.
So here's how a simple BlogServlet would look:
[updated: fixed path to JSP, added a note above about attributes].
[update: added Apache license]
Here's the base RestServlet class. In real life this class also has convenience methods to send redirects, and other standard HTTP stuff.
Here's the ResourceIdentifier class. It tells us how to map URI patterns to handlers, and maps matched pattern groups to attribute names
The RequestHandler interface is the merest slip of a thing:
It is a micro framework. You subclass RestServlet and declare some URL patterns to match, and handlers for them. The base class parses the URI, sets attributes in the ServletRequest object based on the URI pattern, and invokes your handlers.
So here's how a simple BlogServlet would look:
[updated: fixed path to JSP, added a note above about attributes].
[update: added Apache license]
Copyright 2007 Wellstorm Development, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
public class BlogServlet extends RestServlet{
/**
* GET an entry. The base class will populate the "entryId" attribute before calling invoke.
* We told it to do so below, when we defined entryIdentifier.
*/
RequestHandler entryGetHandler = new RequestHandler(){
public void invoke(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// The base class has parsed the URI and populated the request with
// the "entryId" attribute
String entryId = (String)request.getAttribute("entryId");
String forward = "/WEB-INF/entry.jsp";
request.setAttribute("entryHTML", getEntryHTML(entryId));
request.setAttribute("entryTitle", getEntryTitle(entryId));
request.getRequestDispatcher(forward).forward(request, response);
} catch (Exception e) {
response.sendError(404);
}
}
};
/**
* POST a new entry to the collection URI
*/
RequestHandler collectionPostHandler = new RequestHandler(){
public void invoke(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String entryId = saveEntry(request, response);
response.setHeader("Location", buildEntryUri(entryId));
response.setStatus(201);
//write out some HTML or maybe the entry itself.
String forward = "/WEB-INF/entry.jsp";
request.setAttribute("entryHTML", getEntryHTML(entryId));
request.setAttribute("entryTitle", getEntryTitle(entryId));
request.getRequestDispatcher(forward).forward(request, response);
} catch (Exception e) {
response.sendError(404);
}
}
};
//Stubbing these in...
RequestHandler entryPutHandler =null;
RequestHandler entryDeleteHandler = null;
RequestHandler collectionGetHandler = null;
//
// One ResourceIdentifier per URI pattern.
// Each one tells us how to parse the pattern into attributes, and handlers
// for HTTP methods on the resources it identifies.
//
ResourceIdentifier entryIdentifier = new ResourceIdentifier(
"^/(\\d+)$", // URI pattern
new String[] {"entryId"}, // match pattern and insert named request attributes
entryGetHandler, // GET handler for entry URIs
null, // no POST handler for entries
entryPutHandler, // PUT an entry
entryDeleteHandler); // DELETE an entry
ResourceIdentifier collectionIdentifier = new ResourceIdentifier(
"^/$", // URI pattern
collectionGetHandler, // GET handler for collection would list entries
collectionPostHandler); // POST handler for collection will add and entry
@Override
/**
* Here's how we tell our base class how to map URIs to handlers:
*/
protected ResourceIdentifier[] resourceIdentifiers() {
return new ResourceIdentifier[]{entryIdentifier, collectionIdentifier};
}
// these are just stubs... exercise for the reader.
private String buildEntryUri(String entryId) {
return null;
}
private String saveEntry(HttpServletRequest request,
HttpServletResponse response) {
return null;
}
private Object getEntryTitle(String entryId) {
return null;
}
private Object getEntryHTML(String entryId) {
return null;
}
}
Here's the base RestServlet class. In real life this class also has convenience methods to send redirects, and other standard HTTP stuff.
public abstract class RestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger
.getLogger(RestServlet.class.getName());
protected RestServlet() {
super();
}
protected abstract ResourceIdentifier[] resourceIdentifiers();
/** try calling doGet, doPost, or whatever, on each ResourceIdentifier, until one succeeds.
Uses reflection to reduce bloat.
*/
private void doMethod(String methodName, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
boolean did = false;
try {
// The method must be like e.g.
// boolean doGet(HttpServletRequest request, HttpServletResponse response)
Method method = ResourceIdentifier.class.getMethod(methodName, new Class[]{HttpServletRequest.class, HttpServletResponse.class});
Object [] params = new Object[]{request, response};
for (ResourceIdentifier rid : resourceIdentifiers()) {
if (did = (Boolean)method.invoke(rid, params)) {
break;
}
}
if (!did) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception in doPost", e);
throw new ServletException(e);
}
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doMethod ("doPost", request, response);
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doMethod ("doGet", request, response);
}
@Override
protected void doPut(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doMethod ("doPut", request, response);
}
@Override
protected void doDelete(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doMethod ("doDelete", request, response);
}
Here's the ResourceIdentifier class. It tells us how to map URI patterns to handlers, and maps matched pattern groups to attribute names
public class ResourceIdentifier {
private final Pattern pattern;
private final String[] attributeNames;
private final RequestHandler getHandler;
private final RequestHandler putHandler;
private final RequestHandler postHandler;
private final RequestHandler deleteHandler;
public ResourceIdentifier(String regex, String[] attributeNames, RequestHandler getHandler, RequestHandler postHandler,
RequestHandler putHandler, RequestHandler deleteHandler) {
this.pattern = Pattern.compile(regex);
this.attributeNames = attributeNames;
this.getHandler = getHandler;
this.postHandler = postHandler;
this.putHandler = putHandler;
this.deleteHandler = deleteHandler;
}
public ResourceIdentifier(String regex, RequestHandler supportsGet) {
this(regex, new String[] {}, supportsGet, null, null, null);
}
public ResourceIdentifier(String regex, String[] attributeNames, RequestHandler supportsGet) {
this(regex, attributeNames, supportsGet, null, null, null);
}
public ResourceIdentifier(String regex, RequestHandler supportsGet, RequestHandler supportsPost) {
this(regex, new String[] {}, supportsGet, supportsPost, null, null);
}
public ResourceIdentifier(String regex, String[] attributeNames, RequestHandler supportsGet, RequestHandler supportsPost) {
this(regex, attributeNames, supportsGet, supportsPost, null, null);
}
public boolean doGet(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return doMethod(this.getHandler, request.getPathInfo(), request, response);
}
public boolean doPost(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return doMethod(postHandler, request.getPathInfo(), request, response);
}
public boolean doPut (HttpServletRequest request, HttpServletResponse response)
throws Exception {
return doMethod(putHandler, request.getPathInfo(), request, response);
}
public boolean doDelete(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return doMethod(deleteHandler, request.getPathInfo(), request, response);
}
/**
* Test the uri against our pattern. If matched, dispatch to the handler.
*/
private boolean doMethod(RequestHandler handler, String uri, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (uri == null){
uri = "";
}
Matcher matcher = pattern.matcher(uri);
boolean bDid;
if (matcher.matches()) {
if (handler == null) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
bDid = true;
} else {
dispatch(matcher, request, response, handler);
bDid = true;
}
} else {
bDid = false;
}
return bDid;
}
private void dispatch(Matcher matcher, HttpServletRequest request, HttpServletResponse response,
RequestHandler listener) throws Exception {
// The regex matched. Extract all the named attributes from the URL and
// set them as attributes on the request. Then invoke RequestHandler.
int n = matcher.groupCount() ;
if (n != attributeNames.length) {
throw new RuntimeException("must have same number of matches as attribute names");
}
for (int i = 0; i < n; i++) {
request.setAttribute(attributeNames[i], matcher.group(i + 1));
}
listener.invoke(request, response);
}
}
The RequestHandler interface is the merest slip of a thing:
public interface RequestHandler {
void invoke(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Wednesday, August 08, 2007
We don't need PATCH
John Panzer asks:
We could PUT an instance of a new MIME type, atom-update+xml, and the meaning of that document could be: Please selectively update fields of the entry resource, including optional fields and Atom categories.
....if PUT can be used to send just the part you want to change. This can be made to work but has some major problems that make it a poor general choice.
* A PUT to a resource generally means "replace", not "update", so it's semantically surprising.
* In theory it could break write-through caches. (This is probably equivalent to endangering unicorns.)
* It doesn't work for deleting optional fields or updating flexible lists such as Atom categories.
We could PUT an instance of a new MIME type, atom-update+xml, and the meaning of that document could be: Please selectively update fields of the entry resource, including optional fields and Atom categories.
Subscribe to:
Posts (Atom)