Downloading files

Webdav is an extension to HTTP, and in HTTP you "download" files simply by doing a GET request. So when a user uses a webdav client like Windows Explorer to download a file from the server it actually just does a normal GET on that url.

To support GET in Milton you implement GetableResource. The interface looks like this:

public interface GetableResource extends Resource {
    void sendContent( OutputStream out, Range range, Map<String,String> params, String contentType ) throws IOException, NotAuthorizedException, BadRequestException, NotFoundException;

    Long getMaxAgeSeconds(Auth auth);

    String getContentType(String accepts);

    Long getContentLength();
}

Let quickly describe what those methods do:

  • sendContent: This is the main method, it must write the content to the given outputstream. If a range is given then you should skip over bytes and stop at the given byte. Note that range support is very important in some cases, such as streaming videos for HTML5 players. The content type given is that requested by the client, so if you support multiple representations you should choose a format using that. This method may not be called if the resource has not been modified (ie in response to a conditional GET)
  • getMaxAgeSeconds: If you want the resource to be cached return the number of seconds to cache it for, otherwise return null to disable caching
  • getContentType: Called with a list of content types which are acceptable by the client, you should select the best one you support and return this. This will be given to sendContent
  • getContentLength: If you know the resource length return it, otherwise return null. If you return null the framework will either buffer the content to find the length, or send the content with a content length and drop the connection to indicate EOF, both of which have performance impacts - so its best to give a content length if you can.
 

In this example project, the Astronomy Management System, we've actually already implemented GetableResource for the PlanetResource, lets take a look:

    @Override
    public void sendContent(OutputStream out, Range range, Map<String, String> params, String contentType) throws IOException {
        Properties props = new Properties();
        if( planet.getType() == null ) {
            props.setProperty("type", "");
        } else {
            props.setProperty("type", planet.getType());
        }
        props.setProperty("radius", planet.getRadius() + "" );
        props.setProperty("yearLength", planet.getYearLength() + "" );
        props.store(out, null);
    }

You can see its pretty simple, we load the planets properties into a Properties object and then write that to the output.

Generaly sendContent will be implemented in one of two ways:

  • transfer content from storage to the stream, such as when serving digital assets from a content repository or file
  • generate content from a template, such as generating HTML files using JSP/Velocity/etc or generating PDF's on demand