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.
Hugh Winkler holding forth on computing and the Web
Wednesday, August 15, 2007
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.
Tuesday, June 12, 2007
RIA can really suck
Rome Reborn is somebody's Flash wet dream. The content is spectacular (if you're into Roman history, as I am). But some lame brain has encapsulated all the content behind that one URL. I can't give you links to the interesting parts. I have to give you verbal instructions for navigating to different parts of the site. "Wave your mouse over the initial image until the Colosseum is mostly in view, then click, then...." In other words, it's like any desktop application -- which seems to be the idea behind rich internet applications -- "improve" the web until it sucks as much as Windows.
It's maddening, because the site contains tons of useful information -- but none of it is on the web. It's a black hole from which no information can escape.
If new RIA tools don't encourage authors to expose linkable resources, they're just going to continue to be irrelevant to the web.
It's maddening, because the site contains tons of useful information -- but none of it is on the web. It's a black hole from which no information can escape.
If new RIA tools don't encourage authors to expose linkable resources, they're just going to continue to be irrelevant to the web.
Saturday, June 09, 2007
Microsoft vs TestDriven.Net Express
A sad Microsoft drone named Jason is getting famous for the worst reasons. He's a program manager for MS Visual Studio and he's hassling a well meaning fellow named Jamie who wrote a unit testing plugin for VS 2005.
Jamie wrote the plugin using VS 2005 Express, the "free" version, and got enough traction that MS made him an MVP. But then Jason noticed that the plugin supports VS Express. Bad Jamie -- no plugins allowed for Express.
Jamie has published the fatuous correspondence Jason directed his way. They took away his MVP, and MS lawyers are sending cease and desist letters.
Um, the guy is developing free software enhancing the value of all the VS products? Did I mention that part?
Great coverage from The Register.
Jamie wrote the plugin using VS 2005 Express, the "free" version, and got enough traction that MS made him an MVP. But then Jason noticed that the plugin supports VS Express. Bad Jamie -- no plugins allowed for Express.
Jamie has published the fatuous correspondence Jason directed his way. They took away his MVP, and MS lawyers are sending cease and desist letters.
Um, the guy is developing free software enhancing the value of all the VS products? Did I mention that part?
Great coverage from The Register.
Friday, June 08, 2007
Hyperbolism of the Month
...goes to (the envelope please)... Elliotte Rusty Harold!:
I personally don't think you're going to beat the Common Lisp condition system. You can emulate Java-style exception handling, but it's so much richer to be able to restart in the context where the error occurred, without unwinding the stack.
Java’s exception handling is the single best error handling and reporting mechanism ever built into a programming language.
I personally don't think you're going to beat the Common Lisp condition system. You can emulate Java-style exception handling, but it's so much richer to be able to restart in the context where the error occurred, without unwinding the stack.
Wednesday, June 06, 2007
What is Lisa Simpson Doing to that Poor Man?
Once someone has pointed out that interpretation of the London 2012 Olympics logo, it's forever ruined, isn't it? Sorry. (via DPF).


Monday, May 21, 2007
Fifty-four forty or fight!
Today I flunked this quiz question asked of registrants for a Dell sweepstakes:
Where in the U.S. did Dell recently build a Customer Contact Center?
Where in the U.S. did Dell recently build a Customer Contact Center?
- Roseburg, Oregon
- Oklahoma City, Oklahoma
- Edmonton, Alberta
- All of these
Monday, May 07, 2007
And I had never heard of Doug Engelbart!
Since Reginald B. and Alan Kay have urged us to google Doug Engelbart, I reckoned it must be important, so I did. And I found this amazing demo from 1968 of, basically, my computer.
Friday, May 04, 2007
RIA Not Advancing the Ball
Rich widgets obscure the semantics of hypertext. Only the code behind the form knows what the widget really does. Contrast to HTML 5, and in particular Web Forms 2. These guys are extending HTML to capture what we really do on the web. As a consequence, client programs can (or, have a chance to) understand the meanings of hypertext documents from the web.
Example: you want to write a script to automate some remote bookmark service, as part of your mashup. But this service, unlike del.icio.us, has no documented "API". So you have to download its form, complete it programmatically, and POST an entity.
Case 1: The form uses Plain Old HTML. You're golden. All the semantics are right there for you to parse, or read. You identify the name of the text box where you stick the URL, and the name of the text box where you add a description. You compose the URL encoded form data, and POST it to the action URI.
Case 2: The "form" uses Javascript to modify the DOM on the fly: the onload() method adds text boxes, and a submit button, to an empty DOM. In fact, it might not even use the submit button as a form element; when you press the button, its onclick() might send a custom XMLHttpRequest back to the server. Your code will never automate this interaction.
Case 3: The "form" uses XAML + Silverlight plugin. An exacerbated case of (2).
In contrast, Web Forms 2 attempts to capture the semantics of what we do with forms. Because browsers will understand more of the semantics of the form, we can do declaratively what we now have to do in Javascript. For example, lots of HTML forms now have to use script to add a row of controls to a form on the fly ("Click here to add another bookmark"). WF2 captures that as repeating control groups, and the browser can handle it.
(Then again, I am a documented forms nut.)
Mike Dierken justly analogizes: "RIA is to user interfaces as RPC is to messaging interfaces". And notice, it's Rich Internet, not Rich Web Applications. These technologies pay lip service to the web, but they're not advancing the ball toward building more and better links throughout the web information space.
P.S. Wonderful rant by Mark Pilgrim!
Update: fixed a link.
Example: you want to write a script to automate some remote bookmark service, as part of your mashup. But this service, unlike del.icio.us, has no documented "API". So you have to download its form, complete it programmatically, and POST an entity.
Case 1: The form uses Plain Old HTML. You're golden. All the semantics are right there for you to parse, or read. You identify the name of the text box where you stick the URL, and the name of the text box where you add a description. You compose the URL encoded form data, and POST it to the action URI.
Case 2: The "form" uses Javascript to modify the DOM on the fly: the onload() method adds text boxes, and a submit button, to an empty DOM. In fact, it might not even use the submit button as a form element; when you press the button, its onclick() might send a custom XMLHttpRequest back to the server. Your code will never automate this interaction.
Case 3: The "form" uses XAML + Silverlight plugin. An exacerbated case of (2).
In contrast, Web Forms 2 attempts to capture the semantics of what we do with forms. Because browsers will understand more of the semantics of the form, we can do declaratively what we now have to do in Javascript. For example, lots of HTML forms now have to use script to add a row of controls to a form on the fly ("Click here to add another bookmark"). WF2 captures that as repeating control groups, and the browser can handle it.
(Then again, I am a documented forms nut.)
Mike Dierken justly analogizes: "RIA is to user interfaces as RPC is to messaging interfaces". And notice, it's Rich Internet, not Rich Web Applications. These technologies pay lip service to the web, but they're not advancing the ball toward building more and better links throughout the web information space.
P.S. Wonderful rant by Mark Pilgrim!
Update: fixed a link.
Subscribe to:
Posts (Atom)