Saturday, July 30, 2011

HTTP Range header and Download managers

Prerequisite

Java RandomAccessFile Class

Overview

This article details the uses of HTTP Range header and its use in Download managers.

HTTP Range Header

HTTP Range is a available in HTTP/1.1 and allow to specify the range of bytes. It allow you to specify the starting byte and ending byte of the requested stream.
Range header allow many type of ranges we have used only byte range. Here is the syntax of byte range "Range: bytes=1-100", here 0 is starting byte and 100 is ending byte if ending byte is missing (Range: bytes=1-) it will be taken as the last byte in the file(EOF).
For more information on Range header click here


Range header used in Download manager

  • Continuing the download: If you are downloading and download break in the middle because of some external or internal reason, without range header you have to start from the beginning range header allow you to specify the starting byte of the download.
  • Multi-threading download: you can divide the download of large file into multiple chunks and download them in parallel.
    Below is the code which divide the file in chunks and download in parallel, this program after configuring properly gives you 50% better performance then Orbit download manager.
package www.directi.com.junk;

import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;

/*
* this class downloads a file from from given URL in multiple parts. It uses http range header
* $author = Paras Malik(masterofmasters22@gmail.com)
* $date = june 25, 2011
* */

public class DownloadInChunks implements Runnable {
    private static String fileURL = "sourceFileLink"; //file to be downloaded
    private static final String targetFileName = "targetFileName";
    private static final int MAX_BUFFER_SIZE = 1024 * 64 * 4; //maximum number of bytes for which connection will be probed
    private static int chunkOfFile = 1024 * 1024;  //size of one chunk of file to be downloaded
    private int downloadStartingByte;
    private int downloadEndingByte;
    static int fs;

    public DownloadInChunks(int downloadStartingByte, int downloadEndingByte) {
        this.downloadStartingByte = downloadStartingByte;
        this.downloadEndingByte = downloadEndingByte;
    }

    public static void main(String s[]) throws IOException {
        System.out.println(new Date());
        int downloadStartingByte = 0;
        int downloadEndingByte = chunkOfFile;
        int fileSize = getFileSize();
        fs = fileSize;
        while (fileSize > downloadStartingByte) {
            if (downloadEndingByte == fileSize)
                new Thread(new DownloadInChunks(downloadStartingByte, -1)).start();
            else
                new Thread(new DownloadInChunks(downloadStartingByte, downloadEndingByte)).start();
            downloadStartingByte = downloadEndingByte;
            if (downloadEndingByte + chunkOfFile <= fileSize)
                downloadEndingByte = downloadEndingByte + chunkOfFile;
            else
                downloadEndingByte = fileSize;
        }
    }

    static int getFileSize() throws IOException {
        URL url = new URL(fileURL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.connect();
        if (connection.getResponseCode() != 200) {
            System.out.println("Error return code != 200");
        }
        int contentLength = connection.getContentLength();
        connection.disconnect();
        return contentLength;
    }

    public void downloadPartOfFile(int startingByte, String endingByte) throws IOException {
        URL url = new URL(fileURL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        int downloaded = 0;
        connection.setRequestProperty("Range", "bytes=" + startingByte + "-" + endingByte); //is endingByte is empty string http request all the bytes till EOF from given starting byte.
        connection.connect();
        if (connection.getResponseCode() != 206) {   // http return 206 as signal of OK if request is send with range header
            System.out.println("Error return code != 206");
        }
        int contentLength = connection.getContentLength();
        RandomAccessFile file = new RandomAccessFile(targetFileName, "rw");
        file.seek(startingByte);
        InputStream stream = connection.getInputStream();
        while (true) {
            byte buffer[];
            if (contentLength - downloaded > MAX_BUFFER_SIZE) {
                buffer = new byte[MAX_BUFFER_SIZE];
            } else {
                buffer = new byte[contentLength - downloaded];
            }
            int read = stream.read(buffer);
            if (read == -1 || downloaded == contentLength)
                break;
            file.write(buffer, 0, read);
            downloaded += read;
        }
        file.close();
    }

    @Override
    public void run() {
        try {
            if (downloadEndingByte == -1)
                downloadPartOfFile(downloadStartingByte, "");
            else
                downloadPartOfFile(downloadStartingByte, String.valueOf(downloadEndingByte));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


No comments:

Post a Comment