// // Copyright (c) 2007, Brian Frank and Andy Frank // Licensed under the Academic Free License version 3.0 // // History: // 28 Jul 07 Brian Frank Creation // ** ** FileWeblet is used to service an HTTP request on a `sys::File`. ** It handles all the dirty details for cache control, modification ** time, ETags, etc. ** ** Current implementation supports ETags and Modification time ** for cache validation. It does not specify any cache control ** directives. ** @webView=[File#] class FileWeblet : Weblet { ////////////////////////////////////////////////////////////////////////// // Access ////////////////////////////////////////////////////////////////////////// ** ** The file being serviced by this FileWeblet (initialized in service). ** readonly File? file ** ** Get the modified time of the file floored to 1 second ** which is the most precise that HTTP can deal with. ** virtual DateTime modified() { return file.modified.floor(1sec) } ** ** Compute the ETag for the file being serviced which uniquely ** identifies the file version. The default implementation is ** a hash of the modified time and the file size. The result ** of this method must conform to the ETag syntax and be ** wrapped in quotes. ** virtual Str etag() { return "\"" + file.size.toHex + "-" + file.modified.ticks.toHex + "\"" } ////////////////////////////////////////////////////////////////////////// // Weblet ////////////////////////////////////////////////////////////////////////// ** ** Handle GET request for the file. ** override Void service() { this.file = (File)req.resource if (this.file.isDir) throw Err("FileWeblet cannot process dir") super.service } ** ** Handle GET request for the file. ** override Void onGet() { // set identity headers res.headers["ETag"] = etag // TODO try res.headers["Last-Modified"] = modified.toHttpStr catch (Err e) { echo("## ## ## ERROR: FileWeblet.onGet ## file.modified: $file.modified ## modified: $modified ## $e ##") e.trace } // check if we can return a 304 not modified if (checkNotModified) return // service a normal 200 res.statusCode = 200 mime := file.mimeType if (mime != null) res.headers["Content-Type"] = mime.toStr res.headers["Content-Length"] = file.size.toStr file.in.pipe(res.out, file.size) } ** ** Check if the request passed headers indicating it has ** cached version of the file. If the file has not been ** modified, then service the request as 304 and return ** true. This method supports ETag "If-None-Match" and ** "If-Modified-Since" modification time. ** virtual protected Bool checkNotModified() { // check If-Match-None matchNone := req.headers["If-None-Match"] if (matchNone != null) { etag := this.etag match := WebUtil.parseList(matchNone).any |Str s->Bool| { return s == etag || s == "*" } if (match) { res.statusCode = 304 return true } } // check If-Modified-Since since := req.headers["If-Modified-Since"] if (since != null) { sinceTime := DateTime.fromHttpStr(since, false) if (modified == sinceTime) { res.statusCode = 304 return true } } // gotta do it the hard way return false } }