tds/src/main/java/thredds/servlet/ServletUtil.java
public static void returnFile(HttpServletRequest req, HttpServletResponse res, File file, String contentType) throws IOException {
    res.setContentType(contentType);
    res.addDateHeader("Last-Modified", file.lastModified());
    // res.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

    // see if its a Range Request
    boolean isRangeRequest = false;
    long startPos = 0, endPos = Long.MAX_VALUE;
    String rangeRequest = req.getHeader("Range");
    if (rangeRequest != null) { // bytes=12-34 or bytes=12-
      int pos = rangeRequest.indexOf("=");
      if (pos > 0) {
        int pos2 = rangeRequest.indexOf("-");
        if (pos2 > 0) {
          String startString = rangeRequest.substring(pos + 1, pos2);
          String endString = rangeRequest.substring(pos2 + 1);
          startPos = Long.parseLong(startString);
          if (endString.length() > 0)
            endPos = Long.parseLong(endString) + 1;
          isRangeRequest = true;
        }
      }
    }

    // set content length
    long fileSize = file.length();
    long contentLength = fileSize;
    if (isRangeRequest) {
      endPos = Math.min(endPos, fileSize);
      contentLength = endPos - startPos;
    }

    // when compression is turned on, ContentLength has to be overridden
    // this is also true for HEAD, since this must be the same as GET without the body
    if (contentLength > Integer.MAX_VALUE)
      res.addHeader("Content-Length", Long.toString(contentLength));  // allow content length > MAX_INT
    else
      res.setContentLength((int) contentLength);

    String filename = file.getPath();
    // indicate we allow Range Requests
    res.addHeader("Accept-Ranges", "bytes");

    if (req.getMethod().equals("HEAD")) {
      return;
    }

    try {

      if (isRangeRequest) {
        // set before content is sent
        res.addHeader("Content-Range", "bytes " + startPos + "-" + (endPos - 1) + "/" + fileSize);
        res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        try (RandomAccessFile craf = RandomAccessFile.acquire(filename)) {
          IO.copyRafB(craf, startPos, contentLength, res.getOutputStream(), new byte[60000]);
          return;
        }
      }

      // Return the file
      ServletOutputStream out = res.getOutputStream();
      IO.copyFileB(file, out, 60 * 1000);
      /* try (WritableByteChannel cOut = Channels.newChannel(out)) {
        IO.copyFileWithChannels(file, cOut);
        res.flushBuffer();
      } */
    }

    // @todo Split up this exception handling: those from file access vs those from dealing with response
    //       File access: catch and res.sendError()
    //       response: don't catch (let bubble up out of doGet() etc)
    catch (FileNotFoundException e) {
      log.error("returnFile(): FileNotFoundException= " + filename);
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (java.net.SocketException e) {
      log.info("returnFile(): SocketException sending file: " + filename + " " + e.getMessage());
    } catch (IOException e) {
      String eName = e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.debug("returnFile(): ClientAbortException while sending file: " + filename + " " + e.getMessage());
        return;
      }

      if (e.getMessage().startsWith("File transfer not complete")) { // coming from FileTransfer.transferTo()
        log.debug("returnFile() "+e.getMessage());
        return;
      }

      log.error("returnFile(): IOException (" + e.getClass().getName() + ") sending file ", e);
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending file: " + e.getMessage());
    }
  }
public static void returnFile(HttpServletRequest req, HttpServletResponse res, File file, String contentType) throws IOException {
    res.setContentType(contentType);
    res.addDateHeader("Last-Modified", file.lastModified());
    // res.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

    // see if its a Range Request
    boolean isRangeRequest = false;
    long startPos = 0, endPos = Long.MAX_VALUE;
    String rangeRequest = req.getHeader("Range");
    if (rangeRequest != null) { // bytes=12-34 or bytes=12-
      int pos = rangeRequest.indexOf("=");
      if (pos > 0) {
        int pos2 = rangeRequest.indexOf("-");
        if (pos2 > 0) {
          String startString = rangeRequest.substring(pos + 1, pos2);
          String endString = rangeRequest.substring(pos2 + 1);
          startPos = Long.parseLong(startString);
          if (endString.length() > 0)
            endPos = Long.parseLong(endString) + 1;
          isRangeRequest = true;
        }
      }
    }

    // set content length
    long fileSize = file.length();
    long contentLength = fileSize;
    if (isRangeRequest) {
      endPos = Math.min(endPos, fileSize);
      contentLength = endPos - startPos;
    }

    // when compression is turned on, ContentLength has to be overridden
    // this is also true for HEAD, since this must be the same as GET without the body
    if (contentLength > Integer.MAX_VALUE)
      res.addHeader("Content-Length", Long.toString(contentLength));  // allow content length > MAX_INT
    else
      res.setContentLength((int) contentLength);

    String filename = file.getPath();
    // indicate we allow Range Requests
    res.addHeader("Accept-Ranges", "bytes");

    if (req.getMethod().equals("HEAD")) {
      return;
    }

    try {

      if (isRangeRequest) {
        // set before content is sent
        res.addHeader("Content-Range", "bytes " + startPos + "-" + (endPos - 1) + "/" + fileSize);
        res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        try (RandomAccessFile craf = RandomAccessFile.acquire(filename)) {
          IO.copyRafB(craf, startPos, contentLength, res.getOutputStream(), new byte[60000]);
          return;
        }
      }

      // Return the file
      ServletOutputStream out = res.getOutputStream();
      IO.copyFileB(file, out, 60 * 1000);
      /* try (WritableByteChannel cOut = Channels.newChannel(out)) {
        IO.copyFileWithChannels(file, cOut);
        res.flushBuffer();
      } */
    }

    // @todo Split up this exception handling: those from file access vs those from dealing with response
    //       File access: catch and res.sendError()
    //       response: don't catch (let bubble up out of doGet() etc)
    catch (FileNotFoundException e) {
      log.error("returnFile(): FileNotFoundException= " + filename);
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (java.net.SocketException e) {
      log.info("returnFile(): SocketException sending file: " + filename + " " + e.getMessage());
    } catch (IOException e) {
      String eName = e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.debug("returnFile(): ClientAbortException while sending file: " + filename + " " + e.getMessage());
        return;
      }

      if (e.getMessage().startsWith("File transfer not complete")) { // coming from FileTransfer.transferTo()
        log.debug("returnFile() "+e.getMessage());
        return;
      }

      log.error("returnFile(): IOException (" + e.getClass().getName() + ") sending file ", e);
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending file: " + e.getMessage());
    }
  }
public static void returnFile ( HttpServletRequest req , HttpServletResponse res , File file , String contentType ) throws IOException { res . setContentType ( contentType ) ; res . addDateHeader ( "Last-Modified" , file . lastModified ( ) ) ; // res.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
 // see if its a Range Request
 boolean isRangeRequest = false ; long startPos = 0 , endPos = Long . MAX_VALUE ; String rangeRequest = req . getHeader ( "Range" ) ; if ( rangeRequest != null ) { // bytes=12-34 or bytes=12-
 int pos = rangeRequest . indexOf ( "=" ) ; if ( pos > 0 ) { int pos2 = rangeRequest . indexOf ( "-" ) ; if ( pos2 > 0 ) { String startString = rangeRequest . substring ( pos + 1 , pos2 ) ; String endString = rangeRequest . substring ( pos2 + 1 ) ; startPos = Long . parseLong ( startString ) ; if ( endString . length ( ) > 0 ) endPos = Long . parseLong ( endString ) + 1 ; isRangeRequest = true ; } } } // set content length
 long fileSize = file . length ( ) ; long contentLength = fileSize ; if ( isRangeRequest ) { endPos = Math . min ( endPos , fileSize ) ; contentLength = endPos - startPos ; } // when compression is turned on, ContentLength has to be overridden
 // this is also true for HEAD, since this must be the same as GET without the body
 if ( contentLength > Integer . MAX_VALUE ) res . addHeader ( "Content-Length" , Long . toString ( contentLength ) ) ; // allow content length > MAX_INT
 else res . setContentLength ( ( int ) contentLength ) ; String filename = file . getPath ( ) ; // indicate we allow Range Requests
 res . addHeader ( "Accept-Ranges" , "bytes" ) ; if ( req . getMethod ( ) . equals ( "HEAD" ) ) { return ; } try { if ( isRangeRequest ) { // set before content is sent
 res . addHeader ( "Content-Range" , "bytes " + startPos + "-" + ( endPos - 1 ) + "/" + fileSize ) ; res . setStatus ( HttpServletResponse . SC_PARTIAL_CONTENT ) ; try ( RandomAccessFile craf = RandomAccessFile . acquire ( filename ) ) { IO . copyRafB ( craf , startPos , contentLength , res . getOutputStream ( ) , new byte [ 60000 ] ) ; return ; } } // Return the file
 ServletOutputStream out = res . getOutputStream ( ) ; IO . copyFileB ( file , out , 60 * 1000 ) ; /* try (WritableByteChannel cOut = Channels.newChannel(out)) {
        IO.copyFileWithChannels(file, cOut);
        res.flushBuffer();
      } */ } // @todo Split up this exception handling: those from file access vs those from dealing with response
 //       File access: catch and res.sendError()
 //       response: don't catch (let bubble up out of doGet() etc)
 catch ( FileNotFoundException e ) { log . error ( "returnFile(): FileNotFoundException= " + filename ) ; if ( ! res . isCommitted ( ) ) res . sendError ( HttpServletResponse . SC_NOT_FOUND ) ; } catch ( java . net . SocketException e ) { log . info ( "returnFile(): SocketException sending file: " + filename + " " + e . getMessage ( ) ) ; } catch ( IOException e ) { String eName = e . getClass ( ) . getName ( ) ; // dont want compile time dependency on ClientAbortException
 if ( eName . equals ( "org.apache.catalina.connector.ClientAbortException" ) ) { log . debug ( "returnFile(): ClientAbortException while sending file: " + filename + " " + e . getMessage ( ) ) ; return ; } if ( e . getMessage ( ) . startsWith ( "File transfer not complete" ) ) { // coming from FileTransfer.transferTo()
 log . debug ( "returnFile() " + e . getMessage ( ) ) ; return ; } log . error ( "returnFile(): IOException (" + e . getClass ( ) . getName ( ) + ") sending file " , e ) ; if ( ! res . isCommitted ( ) ) res . sendError ( HttpServletResponse . SC_NOT_FOUND , "Problem sending file: " + e . getMessage ( ) ) ; } }
Write a file to the response stream. Handles Range requests.

@param req         request
@param res         response
@param file        must exist and not be a directory
@param contentType must not be null
@throws IOException or error
Write a file to the response stream . Handles Range requests .