聶永的博客

          記錄工作/學(xué)習(xí)的點(diǎn)點(diǎn)滴滴。

          Servlet 3.0筆記之文件下載的那點(diǎn)事情

          使用Servlet 3.0 提供文件下載,當(dāng)然了任何Servlet版本,都可以做到,這里僅僅作為知識(shí)的積累。下面貼出代碼,防止忘卻。

          一。常規(guī)方式文件下載示范


          很普通,簡(jiǎn)單易用,不多說。一般情況下,夠用了。

          二。偽零拷貝(zero copy)方式文件下載示范

          變化不大,關(guān)鍵代碼在于:利用
          FileChannel.transferTo(long position, long count, WritableByteChannel target)
          方法達(dá)到零拷貝(zero copy)目的。把HttpServletResponse的輸出對(duì)象(ServletOutputStream)利用Channels.newChannel(OutputStream out)工具,構(gòu)建一個(gè)WritableByteChannel對(duì)象而已。
          OutputStream out = response.getOutputStream();
          WritableByteChannel outChannel = Channels.newChannel(out);
          測(cè)試代碼:

          心存疑慮的是,這個(gè)是偽零拷貝方式實(shí)現(xiàn)。查看一下Channels.newChannel的源碼:
          public static WritableByteChannel newChannel(final OutputStream out) {
          if (out == null) {
          throw new NullPointerException();
          }

          if (out instanceof FileOutputStream &&
          FileOutputStream.class.equals(out.getClass())) {
          return ((FileOutputStream)out).getChannel();
          }

          return new WritableByteChannelImpl(out);
          }
          因?yàn)檩斎氲姆椒▍?shù)為ServletOutputStream類型實(shí)例,因此只能返回一個(gè)新構(gòu)建的WritableByteChannelImpl對(duì)象。具體構(gòu)建:
          private static class WritableByteChannelImpl
          extends AbstractInterruptibleChannel // Not really interruptible
          implements WritableByteChannel
          {
          OutputStream out;
          private static final int TRANSFER_SIZE = 8192;
          private byte buf[] = new byte[0];
          private boolean open = true;
          private Object writeLock = new Object();

          WritableByteChannelImpl(OutputStream out) {
          this.out = out;
          }

          public int write(ByteBuffer src) throws IOException {
          int len = src.remaining();
          int totalWritten = 0;
          synchronized (writeLock) {
          while (totalWritten < len) {
          int bytesToWrite = Math.min((len - totalWritten),
          TRANSFER_SIZE);
          if (buf.length < bytesToWrite)
          buf = new byte[bytesToWrite];
          src.get(buf, 0, bytesToWrite);
          try {
          begin();
          out.write(buf, 0, bytesToWrite);
          } finally {
          end(bytesToWrite > 0);
          }
          totalWritten += bytesToWrite;
          }
          return totalWritten;
          }
          }

          protected void implCloseChannel() throws IOException {
          out.close();
          open = false;
          }
          }
          很顯然,也是屬于內(nèi)存類型的拷貝了,只能算作偽零拷貝實(shí)現(xiàn)了。

          三。轉(zhuǎn)發(fā)到文件服務(wù)器上

          一般常識(shí)為,讓最擅長(zhǎng)的人來做最擅長(zhǎng)的事情,是為高效。使用類如Nginx高效的Web服務(wù)器專門處理文件下載業(yè)務(wù),達(dá)到零拷貝的目的,也是最佳搭配組合。Nginx可以利用header元數(shù)據(jù)X-Accel-Redirect來控制文件下載行為,甚是不錯(cuò)。利用JAVA進(jìn)行業(yè)務(wù)邏輯判斷,若符合規(guī)則,則提交給Nginx進(jìn)行處理文件的下載,否則,返回給終端用戶權(quán)限不夠等信息。
          用于控制用戶是否具有資格進(jìn)行文件下載業(yè)務(wù)的控制器:

          當(dāng)然,這個(gè)僅僅用于演示,邏輯簡(jiǎn)單。因?yàn)樾枰蚽ginx服務(wù)器進(jìn)行配合,構(gòu)建一個(gè)Server,其配置文件:

          我們?cè)趎ginx配置文件中,設(shè)置/dowloads/目錄是不允許直接訪問的,必須經(jīng)由/download/控制器進(jìn)行轉(zhuǎn)發(fā)方可。經(jīng)測(cè)試,中文名不會(huì)出現(xiàn)亂碼問題,保存的文件也是我們所請(qǐng)求的文件,同名,也不會(huì)出現(xiàn)亂碼問題。但是,若在后臺(tái)獲取文件名,用于顯示/輸出,則需要從ISO-8859-1解碼成GBK編碼方可。
          但這樣做,可能被綁定到某個(gè)類型的服務(wù)器,但也值得。實(shí)際上切換到Apache也是很簡(jiǎn)單的。
          PS : Apache服務(wù)器誒則對(duì)應(yīng)X-Sendfile頭部屬性,因很長(zhǎng)時(shí)間不再使用Apache,這里不再測(cè)試。
          源碼下載
          參考資料:
          1. 通過零拷貝實(shí)現(xiàn)有效數(shù)據(jù)傳輸

          2. Most effective way to write File to ServletOutputStream
          3. Java NIO FileChannel versus FileOutputstream performance / usefulness
          4. NginxChsXSendfile

          posted on 2012-01-19 10:21 nieyong 閱讀(2063) 評(píng)論(0)  編輯  收藏 所屬分類: Servlet3Java

          公告

          所有文章皆為原創(chuàng),若轉(zhuǎn)載請(qǐng)標(biāo)明出處,謝謝~

          新浪微博,歡迎關(guān)注:

          導(dǎo)航

          <2012年1月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          統(tǒng)計(jì)

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個(gè)人收藏

          最新隨筆

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 连州市| 深圳市| 嵩明县| 肥东县| 甘谷县| 宝坻区| 耒阳市| 晋宁县| 澜沧| 昌邑市| 大冶市| 信宜市| 沂水县| 丰原市| 无极县| 峨山| 宁陕县| 百色市| 罗田县| 罗山县| 开化县| 辽宁省| 礼泉县| 醴陵市| 襄汾县| 红原县| 穆棱市| 宜君县| 含山县| 吉隆县| 绥滨县| 陆丰市| 灵石县| 井研县| 基隆市| 邳州市| 太仓市| 肇州县| 宁国市| 浦城县| 辽宁省|