Skip to content

Instantly share code, notes, and snippets.

@stormwild
Last active April 4, 2024 06:45
Show Gist options
  • Save stormwild/040d446d398c158db09cfc072c4cb4ab to your computer and use it in GitHub Desktop.
Save stormwild/040d446d398c158db09cfc072c4cb4ab to your computer and use it in GitHub Desktop.

Web Api

Download Resume

make download resumable using c#

Resuming files is done by specifying the byte range of the file you would like to download using the Range HTTP header. This can be done in .NET with the HttpWebRequest.AddRange function.

For example:

request.AddRange(1000); 

how resume able file download in asp.net with c# -> best way (for large files too)

downloading with pause and resume

File Upload and Download in Angular 9 With Web API and SQL

Android

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    if(ISSUE_DOWNLOAD_STATUS.intValue()==ECMConstant.ECM_DOWNLOADING){
        File file=new File(DESTINATION_PATH);
        if(file.exists()){
             downloaded = (int) file.length();
             connection.setRequestProperty("Range", "bytes="+(file.length())+"-");
        }
    }else{
        connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
    }
    connection.setDoInput(true);
    connection.setDoOutput(true);
    progressBar.setMax(connection.getContentLength());
     in = new BufferedInputStream(connection.getInputStream());
     fos=(downloaded==0)? new FileOutputStream(DESTINATION_PATH): new FileOutputStream(DESTINATION_PATH,true);
     bout = new BufferedOutputStream(fos, 1024);
    byte[] data = new byte[1024];
    int x = 0;
    while ((x = in.read(data, 0, 1024)) >= 0) {
        bout.write(data, 0, x);
         downloaded += x;
         progressBar.setProgress(downloaded);
    }
/*
 * @param callback = To update the UI with appropriate action
 * @param fileName = Name of the file by which downloaded file will be saved.
 * @param downloadURL = File downloading URL
 * @param filePath = Path where file will be saved
 * @param object = Any object you want in return after download is completed to do certain operations like insert in DB or show toast
 */

public void startDownload(final IDownloadCallback callback, String fileName, String downloadURL, String filePath, Object object) {
    callback.onPreExecute(); // Callback to tell that the downloading is going to start
    int count = 0;
    File outputFile = null; // Path where file will be downloaded
    try {
        File file = new File(filePath);
        file.mkdirs();
        long range = 0;
        outputFile = new File(file, fileName);
        /**
         * Check whether the file exists or not
         * If file doesn't exists then create the new file and range will be zero.
         * But if file exists then get the length of file which will be the starting range,
         * from where the file will be downloaded
         */
        if (!outputFile.exists()) {
            outputFile.createNewFile();
            range = 0;
        } else {
            range = outputFile.length();
        }
        //Open the Connection
        URL url = new URL(downloadURL);
        URLConnection con = url.openConnection();
        // Set the range parameter in header and give the range from where you want to start the downloading
        con.setRequestProperty("Range", "bytes=" + range + "-");
        /**
         * The total length of file will be the total content length given by the server + range.
         * Example: Suppose you have a file whose size is 1MB and you had already downloaded 500KB of it.
         * Then you will pass in Header as "Range":"bytes=500000".
         * Now the con.getContentLength() will be 500KB and range will be 500KB.
         * So by adding the two you will get the total length of file which will be 1 MB
         */
        final long lenghtOfFile = (int) con.getContentLength() + range;

        FileOutputStream fileOutputStream = new FileOutputStream(outputFile, true);
        InputStream inputStream = con.getInputStream();

        byte[] buffer = new byte[1024];

        long total = range;
        /**
         * Download the save the content into file
         */
        while ((count = inputStream.read(buffer)) != -1) {
            total += count;
            int progress = (int) (total * 100 / lenghtOfFile);
            EntityDownloadProgress entityDownloadProgress = new EntityDownloadProgress();
            entityDownloadProgress.setProgress(progress);
            entityDownloadProgress.setDownloadedSize(total);
            entityDownloadProgress.setFileSize(lenghtOfFile);
            callback.showProgress(entityDownloadProgress);
            fileOutputStream.write(buffer, 0, count);
        }
        //Close the outputstream
        fileOutputStream.close();
        // Disconnect the Connection
        if (con instanceof HttpsURLConnection) {
            ((HttpsURLConnection) con).disconnect();
        } else if (con instanceof HttpURLConnection) {
            ((HttpURLConnection) con).disconnect();
        }
        inputStream.close();
        /**
         * If file size is equal then return callback as success with downlaoded filepath and the object
         * else return failure
         */
        if (lenghtOfFile == outputFile.length()) {
            callback.onSuccess(outputFile.getAbsolutePath(), object);
        } else {
            callback.onFailure(object);
        }
    } catch (Exception e) {
        e.printStackTrace();
        callback.onFailure(object);
    }
}
 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    if(ISSUE_DOWNLOAD_STATUS.intValue()==ECMConstant.ECM_DOWNLOADING){
        File file=new File(DESTINATION_PATH);
        if(file.exists()){
             downloaded = (int) file.length();
             connection.setRequestProperty("Range", "bytes="+(file.length())+"-");
        }
    }else{
        connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
    }
    connection.setDoInput(true);
    connection.setDoOutput(true);
    progressBar.setMax(connection.getContentLength());
     in = new BufferedInputStream(connection.getInputStream());
     fos=(downloaded==0)? new FileOutputStream(DESTINATION_PATH): new FileOutputStream(DESTINATION_PATH,true);
     bout = new BufferedOutputStream(fos, 1024);
    byte[] data = new byte[1024];
    int x = 0;
    while ((x = in.read(data, 0, 1024)) >= 0) {
        bout.write(data, 0, x);
         downloaded += x;
         progressBar.setProgress(downloaded);
    }

Server

PHP

By default all HTTP/1.1 compliant servers accept range headers and respond with content-range headers. (sysadmin part)

You don't need to change anything on webserver for ul/dl. Everything depends on how clients are downloading and uploading files. Maybe it's not the mechanism what you are looking for. (development part)

I think this is question for https://stackoverflow.com/

.NET

static void DownloadFile(string sSourceURL, string sDestinationPath)
{
    long iFileSize = 0;
    int iBufferSize = 1024;
    iBufferSize *= 1000;
    long iExistLen = 0;
    System.IO.FileStream saveFileStream;
    if (System.IO.File.Exists(sDestinationPath))
    {
        System.IO.FileInfo fINfo = 
           new System.IO.FileInfo(sDestinationPath);
        iExistLen = fINfo.Length;
    }
    if (iExistLen > 0)
        saveFileStream = new System.IO.FileStream(sDestinationPath, 
          System.IO.FileMode.Append, System.IO.FileAccess.Write, 
          System.IO.FileShare.ReadWrite);
    else
        saveFileStream = new System.IO.FileStream(sDestinationPath, 
          System.IO.FileMode.Create, System.IO.FileAccess.Write, 
          System.IO.FileShare.ReadWrite);
 
    System.Net.HttpWebRequest hwRq;
    System.Net.HttpWebResponse hwRes;
    hwRq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(sSourceURL);
    hwRq.AddRange((int)iExistLen);
    System.IO.Stream smRespStream;
    hwRes = (System.Net.HttpWebResponse)hwRq.GetResponse();
    smRespStream = hwRes.GetResponseStream();
 
    iFileSize = hwRes.ContentLength;
 
    int iByteSize;
    byte[] downBuffer = new byte[iBufferSize];
 
    while ((iByteSize = smRespStream.Read(downBuffer, 0, downBuffer.Length)) > 0)
    {
        saveFileStream.Write(downBuffer, 0, iByteSize);
    }
}
public static bool DownloadFileMethod(HttpContext httpContext, string filePath, long speed)
{
    // Many changes: mostly declare variables near use
    // Extracted duplicate references to HttpContext.Response and .Request
    // also duplicate reference to .HttpMethod

    // Removed try/catch blocks which hid any problems
    var response = httpContext.Response;
    var request = httpContext.Request;
    var method = request.HttpMethod.ToUpper();
    if (method != "GET" &&
        method != "HEAD")
    {
        response.StatusCode = 501;
        return false;
    }

    if (!File.Exists(filePath))
    {
        response.StatusCode = 404;
        return false;
    }

    // Stream implements IDisposable so should be in a using block
    using (var myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        var fileLength = myFile.Length;
        if (fileLength > Int32.MaxValue)
        {
            response.StatusCode = 413;
            return false;
        }

        var lastUpdateTiemStr = File.GetLastWriteTimeUtc(filePath).ToString("r");
        var fileName = Path.GetFileName(filePath);
        var fileNameUrlEncoded = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
        var eTag = fileNameUrlEncoded + lastUpdateTiemStr;

        var ifRange = request.Headers["If-Range"];
        if (ifRange != null && ifRange.Replace("\"", "") != eTag)
        {
            response.StatusCode = 412;
            return false;
        }

        long startBytes = 0;

        // Just guessing, but I bet you want startBytes calculated before
        // using to calculate content-length
        var rangeHeader = request.Headers["Range"];
        if (rangeHeader != null)
        {
            response.StatusCode = 206;
            var range = rangeHeader.Split(new[] {'=', '-'});
            startBytes = Convert.ToInt64(range[1]);
            if (startBytes < 0 || startBytes >= fileLength)
            {
                // TODO: Find correct status code
                response.StatusCode = (int) HttpStatusCode.BadRequest;
                response.StatusDescription =
                    string.Format("Invalid start of range: {0}", startBytes);
                return false;
            }
        }

        response.Clear();
        response.Buffer = false;
        response.AddHeader("Content-MD5", GetMD5Hash(filePath));
        response.AddHeader("Accept-Ranges", "bytes");
        response.AppendHeader("ETag", string.Format("\"{0}\"", eTag));
        response.AppendHeader("Last-Modified", lastUpdateTiemStr);
        response.ContentType = "application/octet-stream";
        response.AddHeader("Content-Disposition", "attachment;filename=" +
                                                    fileNameUrlEncoded.Replace("+", "%20"));
        var remaining = fileLength - startBytes;
        response.AddHeader("Content-Length", remaining.ToString());
        response.AddHeader("Connection", "Keep-Alive");
        response.ContentEncoding = Encoding.UTF8;

        if (startBytes > 0)
        {
            response.AddHeader("Content-Range",
                                string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength));
        }

        // BinaryReader implements IDisposable so should be in a using block
        using (var br = new BinaryReader(myFile))
        {
            br.BaseStream.Seek(startBytes, SeekOrigin.Begin);

            const int packSize = 1024*10; //read in block,every block 10K bytes
            var maxCount = (int) Math.Ceiling((remaining + 0.0)/packSize); //download in block
            for (var i = 0; i < maxCount && response.IsClientConnected; i++)
            {
                response.BinaryWrite(br.ReadBytes(packSize));
                response.Flush();

                // HACK: Unexplained sleep
                var sleep = (int) Math.Ceiling(1000.0*packSize/speed); //the number of millisecond
                if (sleep > 1) Thread.Sleep(sleep);
            }
        }
    }
    return true;
}

Android HTTP Clients

HERE IS THE LIST OF BEST ANDROID NETWORKING LIBRARIES OR DEPENDENCIES

Range Requests

ASP.NET Web API and HTTP Byte Range Support

public class RangeController : ApiController
{
    // Sample content used to demonstrate range requests
    private static readonly byte[] _content = Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz");

    // Content type for our body
    private static readonly MediaTypeHeaderValue _mediaType = MediaTypeHeaderValue.Parse("text/plain");

    public HttpResponseMessage Get()
    {
        // A MemoryStream is seekable allowing us to do ranges over it. Same goes for FileStream.
        MemoryStream memStream = new MemoryStream(_content);

        // Check to see if this is a range request (i.e. contains a Range header field)
        // Range requests can also be made conditional using the If-Range header field. This can be 
        // used to generate a request which says: send me the range if the content hasn't changed; 
        // otherwise send me the whole thing.
        if (Request.Headers.Range != null)
        {
            try
            {
                HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
                partialResponse.Content = new ByteRangeStreamContent(memStream, Request.Headers.Range, _mediaType);
                return partialResponse;
            }
            catch (InvalidByteRangeException invalidByteRangeException)
            {
                return Request.CreateErrorResponse(invalidByteRangeException);
                throw;
            }
        }
        else
        {
            // If it is not a range request we just send the whole thing as normal
            HttpResponseMessage fullResponse = Request.CreateResponse(HttpStatusCode.OK);
            fullResponse.Content = new StreamContent(memStream);
            fullResponse.Content.Headers.ContentType = _mediaType;
            return fullResponse;
        }
    }
}

Notes

  • ByteRangeStreamContent Class

  • In ASP.NET Web API 2, ByteRangeStreamContent returns incorrect data when used with a stream from Azure Storage

    <dependentAssembly>
          <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.0.0.0" />      
    </dependentAssembly>
      // Including my setup of the range values this time:
      var range = Request.Headers.Range;
    
      long chunkLength = 2500000;
      long? beginRange = range.Ranges.First().From;
      long? endRange = range.Ranges.First().To;
    
      if (endRange == null)
      {
          if ((beginRange + chunkLength) > myBlob.Properties.Length)
          {
              endRange = myBlob.Properties.Length - 1;
          }
          else
          {
              endRange = beginRange + chunkLength;
          }
      }
      var blobStreamPosition = beginRange.Value;
    
      // Set the stream position
      blobStream.Position = blobStreamPosition;
    
      int bytesToRead = (int)(endRange - blobStreamPosition + 1);
    
      // Using BinaryReader for convenience
      BinaryReader binaryReader = new BinaryReader(blobStream);
      byte[] blobByteArray = binaryReader.ReadBytes(bytesToRead);
      message.Content = new ByteArrayContent(blobByteArray);
    
      // Don't forget that now you have to set the content range header yourself:
      message.Content.Headers.ContentRange = new ContentRangeHeaderValue(blobStreamPosition, endRange.Value, myBlob.Properties.Length);
      message.Content.Headers.ContentType = new MediaTypeHeaderValue(myBlob.Properties.ContentType);
    
      binaryReader.Dispose();
      blobStream.Dispose();
  • Returning binary file from controller in ASP.NET Web API

      using System;
      using System.IO;
      using System.Net;
      using System.Net.Http;
      using System.Net.Http.Headers;
      using System.Threading;
      using System.Threading.Tasks;
      using System.Web;
      using System.Web.Http;
    
      class FileResult : IHttpActionResult
      {
          private readonly string _filePath;
          private readonly string _contentType;
    
          public FileResult(string filePath, string contentType = null)
          {
              if (filePath == null) throw new ArgumentNullException("filePath");
    
              _filePath = filePath;
              _contentType = contentType;
          }
    
          public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
          {
              var response = new HttpResponseMessage(HttpStatusCode.OK)
              {
                  Content = new StreamContent(File.OpenRead(_filePath))
              };
    
              var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
              response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
    
              return Task.FromResult(response);
          }
      }
      [Route("Images/{*imagePath}")]
      public IHttpActionResult GetImage(string imagePath)
      {
          var serverPath = Path.Combine(_rootPath, imagePath);
          var fileInfo = new FileInfo(serverPath);
    
          return !fileInfo.Exists
              ? (IHttpActionResult) NotFound()
              : new FileResult(fileInfo.FullName);
      }
  • How to work with HTTP Range Headers in WebAPI

    Range requests enable you to retrieve partial content in lieu of the entire content for improved performance when working with WebAPI over HTTP.

    HTTP 1.1 provides support for range headers – it allows you to specify range headers to enable the client to retrieve partial response. This can be used to retrieve partial content when working over HTTP. It's a technique that's widely used when streaming video files, for instance.

    With this approach, rather than retrieving the entire data at one go, your application can retrieve the data partially, in chunks. This feature can be particularly helpful in scenarios when the client needs to recover the data from interrupted connections that might occur due to cancelled requests, or connections to the server that have been dropped.

    The HTTP 206 Partial Content status code and its associated headers enable the clients or the web browsers to retrieve partial content in lieu of the complete content when a request is sent. Note that most modern-day web servers provide support for range requests. The ByteRangeStreamContent class that has been added to WebAPI makes it easier to work with range requests. The ByteRangeStreamContent class works similarly to how, let's say, StreamContent works – with the exception that it can provide a view over a stream that is seekable. Note that a MemoryStream or a FileStream is seekable, i.e., they can be used to do ranges over them.

    Let’s now implement a simple WebAPI with support for HTTP Range Header using the ByteRangeStreamContent class. Here's a quick glance at how this all would work.

    1. The client connects to the server and requests for partial content by specifying the values in the Range header.
    2. The server responds by sending partial content if range is available with the status code as HTTP 206.
    3. If the requested range is not available, the server responds with HTTP 416.

    And, now the implementation.

    The following code snippet illustrates a WebAPI controller method with support for range headers. For the sake of simplicity I have hard-coded a string as data that would be retrieved as partial response by the client. You can change this implementation to work with data of much larger sizes, like files, large arrays, lists, etc.

    public class Controller : ApiController
    {
        public HttpResponseMessage GetData()
        {
            try
            {
                MemoryStream memoryStream = new MemoryStream(data);
    
                if (Request.Headers.Range != null)
                {
                    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.PartialContent);
    
                    response.Content = new ByteRangeStreamContent(memoryStream, Request.Headers.Range, MediaTypeHeaderValue.Parse("application/json"));
    
                    return response;
                }
                else
                {
                    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
    
                    response.Content = new StreamContent(memoryStream);
    
                    response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
    
                    return response;
                }
    
            }
            catch (Exception ex)
            {
    
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
            }
        }
    }
    
    public class ApiClient
    {
        public void Run()
        {
            using (var httpclient = new HttpClient())
            {
    
                httpclient.DefaultRequestHeaders.Range = new RangeHeaderValue(0, 5);
    
                var response = await httpclient.GetAsync(url, HttpCompletionOption.ResponseContentRead);
    
                using (var stream = await response.Content.ReadAsStreamAsync())
                {
    
                    var data = new byte[5];
    
                    int count = stream.Read(data, 0, data.Length);
    
                    MemoryStream memoryStream = new MemoryStream(data);
    
                    StreamReader reader = new StreamReader(memoryStream);
    
                    string text = reader.ReadToEnd();
                }
    
            }
        }
    }

Progressive Download Support in ASP.NET Web API

Web Api Progressive Download on GitHub and NuGet

WebApi.ProgressiveDownloads

Web API Thoughts 1 of 3 - Data Streaming

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment