nl_tokens: Write a file to the response stream . Handles Range requests .
pl_tokens: 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 . <mask> ( 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 ( ) ) ; } }