官术网_书友最值得收藏!

3.5 生成響應(yīng)

Servlet的核心職責(zé)就是根據(jù)客戶端的請(qǐng)求來(lái)生成動(dòng)態(tài)響應(yīng)。在ServletResponse接口中定義了一系列與生成響應(yīng)結(jié)果相關(guān)的方法,如表3-2所示。

表3-2 ServletResponse接口的主要方法

3.5.1 編碼類型

ServletResponse中響應(yīng)正文的默認(rèn)MIME類型為text/plain,即純文本類型;而HttpServletResponse中響應(yīng)正文的默認(rèn)MIME類型為text/html,即HTML文檔類型。可以通過(guò)調(diào)用getContentType方法獲得當(dāng)前響應(yīng)正文的MIME類型,或者通過(guò)調(diào)用setContentType(String type)來(lái)設(shè)置當(dāng)前響應(yīng)正文的MIME類型。

說(shuō)明:MIME意為多媒體Internet郵件擴(kuò)展,它設(shè)計(jì)的最初目的是為了在發(fā)送電子郵件時(shí)附加多媒體數(shù)據(jù),讓郵件客戶程序能根據(jù)其類型進(jìn)行處理。在最早的HTTP協(xié)議中,并沒(méi)有附加的數(shù)據(jù)類型信息,所有傳送的數(shù)據(jù)都被客戶程序解釋為超文本標(biāo)記語(yǔ)言HTML文檔,而隨著Internet應(yīng)用的不斷擴(kuò)展,為了支持多媒體數(shù)據(jù)類型,HTTP協(xié)議中就使用了附加在文檔之前的MIME數(shù)據(jù)類型信息來(lái)標(biāo)識(shí)數(shù)據(jù)類型。

Web瀏覽器使用MIME類型來(lái)識(shí)別非HTML文檔,并決定如何顯示該文檔內(nèi)的數(shù)據(jù)。如果瀏覽器中安裝了與MIME類型對(duì)應(yīng)的插件(plug-in),則當(dāng)Web瀏覽器下載MIME類型指示的文檔時(shí),就能夠啟動(dòng)相應(yīng)插件處理此文檔。某些MIME類型還可以與外部程序結(jié)合使用,瀏覽器下載文檔后會(huì)啟動(dòng)相應(yīng)的外部程序。有時(shí)候?yàn)g覽器不能識(shí)別文檔的MIME類型,通常這是由于沒(méi)有安裝這些文檔需要的插件而導(dǎo)致的。在這種情況下,瀏覽器會(huì)彈出一個(gè)對(duì)話框,詢問(wèn)用戶是否需要打開(kāi)該文件或是將它保存到本地磁盤上。

通過(guò)調(diào)用setContentType(String type), Servlet可以向?yàn)g覽器返回非HTML文件,比如Adobe PDF和Microsoft Word。使用正確的MIME類型能夠保證這些非HTML文件被正確的插件或外部程序處理顯示。

PDF文件的MIME類型是application/pdf。如果需要Servlet返回PDF文檔,則需要將response對(duì)象中header的content類型設(shè)置成application/pdf。代碼如下:

    res.setContentType("application/pdf");

若要返回一個(gè)Microsoft Word文檔,就要將response對(duì)象的content類型設(shè)置成application/msword。代碼如下:

    res.setContentType("application/msword");

如果是一個(gè)Excel文檔,則使用MIME類型application/vnd.ms-excel。其中vnd表示該應(yīng)用程序的制造者,必須將它包含在MIME類型里才能夠打開(kāi)該類型的文檔。代碼如下:

    res.setContentType("application/vnd.ms-excel");

3.5.2 流操作

在Servlet與客戶的請(qǐng)求應(yīng)答的過(guò)程中,底層是通過(guò)輸入輸出流來(lái)實(shí)現(xiàn)的。Servlet支持兩種格式的輸入輸出流。一種是字符輸入輸出流。ServletResponse的getWriter方法返回一個(gè)PrintWriter對(duì)象,Servlet可以利用PrintWriter來(lái)輸出字符流形式的正文數(shù)據(jù)。另外一種是字節(jié)輸入輸出流。ServletResponse的getOutputStream方法返回一個(gè)ServletOutputStream對(duì)象,Servlet可以利用ServletOutputStream來(lái)輸出二進(jìn)制的正文數(shù)據(jù)。

為了提高輸出數(shù)據(jù)的效率,ServletOutputStream和PrintWriter先把數(shù)據(jù)寫到緩沖區(qū)內(nèi)。當(dāng)緩沖區(qū)內(nèi)的數(shù)據(jù)被提交給客戶后,ServletResponse的isCommitted方法返回true。在以下幾種情況下,緩沖區(qū)內(nèi)的數(shù)據(jù)會(huì)被提交給客戶,即數(shù)據(jù)被發(fā)送到客戶端:

·當(dāng)緩沖區(qū)內(nèi)的數(shù)據(jù)已滿時(shí),ServletOutputStream或PrintWriter會(huì)自動(dòng)把緩沖區(qū)內(nèi)的數(shù)據(jù)發(fā)送給客戶端,并且清空緩沖區(qū)。

·調(diào)用ServletResponse對(duì)象的flushBuffer方法。

·調(diào)用ServletOutputStream或PrintWriter對(duì)象的flush方法或close方法。

為了確保ServletOutputStream或PrintWriter輸出的所有數(shù)據(jù)都會(huì)被提交給客戶,比較安全的做法是在所有數(shù)據(jù)都輸出完畢后,調(diào)用ServletOutputStream或PrintWriter的close方法。

下面編寫一個(gè)返回PDF文件的Servlet來(lái)說(shuō)明Servlet如何實(shí)現(xiàn)向客戶端發(fā)送非HTML文檔,同時(shí)演示Servlet對(duì)輸入輸出流的操作。代碼如程序3-16所示。

程序3-16:PDFServlet.java

    package com.servlet;
    …
    @WebServlet(name = "PDFServlet", urlPatterns = {"/pdfshow"})
    public class PDFServlet extends HttpServlet {
     protected void processRequest(HttpServletRequest request, HttpServletResponse
     response)
              throws ServletException, IOException {
          response.setContentType("application/pdf");
          ServletOutputStream out = response.getOutputStream();
          File pdf = null;
          // BufferedInputStream buf = null;
          byte[] buffer = new byte[1024 * 1024];
          FileInputStream input = null;
          try {
              pdf = new File("c:\\sample.pdf"); //為演示PDF文件發(fā)送而保存的一個(gè)文件
              response.setContentLength((int) pdf.length());
              input = new FileInputStream(pdf);
              int readBytes = -1;
              while ((readBytes = input.read(buffer, 0, 1024 * 1024)) ! = -1) {
                  out.write(buffer, 0, 1024 * 1024);
              }
          } catch (IOException e) {
              System.out.println("file not found! ");
          } finally {
              if (out ! = null) {
                  out.close();
              }
              if (input ! = null) {
                  input.close();
              }
            }
        }
    }
    …
    }

程序說(shuō)明:首先調(diào)用HttpServletResponse接口的setContentType("application/pdf")將響應(yīng)內(nèi)容類型設(shè)置為PDF類型,然后調(diào)用getOutputStream()獲取Servlet輸出流對(duì)象。為使PDF以流的形式輸出到客戶端,先創(chuàng)建一個(gè)File對(duì)象,根據(jù)File對(duì)象得到一個(gè)文件輸入流對(duì)象。通過(guò)將文件輸入流中的信息寫到Servlet輸出流中實(shí)現(xiàn)PDF文件的發(fā)送。為了防止下載的數(shù)據(jù)量過(guò)大,代碼中使用了一個(gè)容量為1MB的緩沖區(qū)。

為運(yùn)行示例程序,需要首先在“C:”盤根目錄下放置一個(gè)PDF文件并將其命名為sample.pdf。打開(kāi)瀏覽器,在地址欄中輸入http://localhost:8080/Chapter3/pdfshow,如果讀者的機(jī)器裝有Acrobat Reader,那么,Servlet發(fā)送到客戶端的PDF文件sample.pdf將在瀏覽器內(nèi)被打開(kāi)。如果沒(méi)有安裝Acrobat Reader,則瀏覽器將提示保存文件。

如果讀者的機(jī)器裝有Acrobat Reader,但只想通過(guò)這種方式傳送到客戶端,而不想在瀏覽器內(nèi)部打開(kāi),那么怎么辦呢?一種叫作content-disposition的HTTP response header(響應(yīng)頭部)允許將文檔指定成單獨(dú)打開(kāi)(而不是在瀏覽器中打開(kāi)),還可以為該文檔建議一個(gè)保存時(shí)的文件名。

在程序3-16的ServletOutputStream out =res.getOutputStream()一行下面添加如下代碼:

    res.setHeader("Content-disposition", "attachment; filename=Example.pdf");

打開(kāi)瀏覽器,以同樣的方式重新調(diào)用Servlet,則出現(xiàn)如圖3-22所示的提示對(duì)話框,PDF被單獨(dú)保存而不是在瀏覽器內(nèi)打開(kāi)。

圖3-22 單獨(dú)保存PDF的提示對(duì)話框

3.5.3 重定向

ServletResponse接口還提供了一個(gè)重要的方法sendRedirect,該方法允許將當(dāng)前請(qǐng)求定位到其他Web組件上,這個(gè)組件甚至可以是其他主機(jī)上的Web組件。在將請(qǐng)求重新定位之前,Servlet可以對(duì)當(dāng)前的請(qǐng)求或響應(yīng)對(duì)象通過(guò)調(diào)用SetAttribute方法來(lái)添加屬性信息。重定向相當(dāng)于通知客戶端重新發(fā)起一個(gè)新的請(qǐng)求,因此重定向后在瀏覽器地址欄中會(huì)出現(xiàn)重定向頁(yè)面的URL。

下面修改程序3-16中的Servlet組件,使它重定向到3.3節(jié)創(chuàng)建的Servlet First,修改后的代碼如程序3-17所示

程序3-17:PDFServlet.java

    package com.servlet;
    …
    @WebServlet(name = "PDFServlet", urlPatterns = {"/pdfshow"})
    public class PDFServlet extends HttpServlet {
        …
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse res)
              throws ServletException, IOException {
          res.sendRedirect("First");
          return;
          }
        …
    }

程序說(shuō)明:在doGet方法中,不再調(diào)用processRequest方法,而是調(diào)用sendRedirect方法實(shí)現(xiàn)請(qǐng)求重定向,并且之后立即調(diào)用了return語(yǔ)句。這是因?yàn)檎?qǐng)求已經(jīng)重定向,若再繼續(xù)操作HttpServletRequest和HttpServletResponse,將會(huì)拋出異常。

運(yùn)行程序3-17,結(jié)果如圖3-23所示。特別要注意圖3-23中瀏覽器中的地址欄信息,看看是否已經(jīng)發(fā)生變化。

圖3-23 重定向?qū)е聻g覽器地址欄變化

還需要注意的是,在調(diào)用sendRedirect方法前不允許有任何信息輸出到客戶端,因?yàn)閃eb容器在Servlet組件已經(jīng)有信息輸出到客戶端的情形下,是不允許進(jìn)行重定向的。

3.5.4 服務(wù)器推送

3.1節(jié)已經(jīng)簡(jiǎn)單介紹了HTTP 1.1協(xié)議。隨著互聯(lián)網(wǎng)的快速發(fā)展,HTTP 1.1協(xié)議得到了迅猛發(fā)展,但當(dāng)一個(gè)頁(yè)面包含了數(shù)十個(gè)請(qǐng)求時(shí),HTTP 1.1協(xié)議的局限性便暴露了出來(lái):

·每個(gè)請(qǐng)求需要單獨(dú)建立與服務(wù)器的連接,浪費(fèi)資源。

·每個(gè)請(qǐng)求與響應(yīng)都需要添加完整的頭信息,應(yīng)用數(shù)據(jù)傳輸效率較低。

·默認(rèn)沒(méi)有進(jìn)行加密,數(shù)據(jù)在傳輸過(guò)程中容易被監(jiān)聽(tīng)與篡改。

HTTP 2正是為了解決HTTP 1.1暴露出來(lái)的問(wèn)題而誕生的。HTTP 2最大的特點(diǎn)是:不會(huì)改動(dòng)HTTP的語(yǔ)義、HTTP方法、狀態(tài)碼、URI及首部字段等核心概念,而是致力于突破上一代標(biāo)準(zhǔn)的性能限制,改進(jìn)傳輸性能,實(shí)現(xiàn)低延遲和高吞吐量。一些知名的網(wǎng)站如www.baidu.com已經(jīng)開(kāi)始全面支持HTTP 2。

HTTP 1.1協(xié)議傳輸?shù)闹饕俏谋拘畔ⅲ鳫TTP 2把HTTP協(xié)議通信的基本單位縮小為一個(gè)一個(gè)的幀,這些幀對(duì)應(yīng)著邏輯流中的消息,并行地在同一個(gè)TCP連接上雙向交換消息。例如,客戶端使用HTTP 2協(xié)議請(qǐng)求頁(yè)面http://www.163.com,則頁(yè)面上所有的資源請(qǐng)求都是通過(guò)客戶端與服務(wù)器之間的一條TCP連接完成請(qǐng)求和響應(yīng)的。

另外HTTP 2新增的一個(gè)強(qiáng)大的功能,就是服務(wù)器可以對(duì)一個(gè)客戶端請(qǐng)求發(fā)送多個(gè)響應(yīng)。換句話說(shuō),服務(wù)器除了對(duì)最初請(qǐng)求的響應(yīng)外,還可以額外向客戶端推送資源,而無(wú)須客戶端明確地請(qǐng)求。例如,當(dāng)客戶端瀏覽器請(qǐng)求一個(gè)HTML文件,服務(wù)器已經(jīng)能夠知道客戶端接下來(lái)要請(qǐng)求頁(yè)面中鏈接的其他資源(如logo圖片、css文件等)了,因此將自動(dòng)推送這些資源給客戶端而不需要等待瀏覽器得到HTML文件后解析頁(yè)面再發(fā)送資源請(qǐng)求。服務(wù)器推送有一個(gè)很大的優(yōu)勢(shì)便是可實(shí)現(xiàn)客戶端緩存。對(duì)于相同的資源,客戶端將可以直接在本地緩存中讀取。由于HTTP 2可主動(dòng)向服務(wù)器端推送數(shù)據(jù),目前各大瀏覽器出于安全考慮,僅支持安全連接下的HTTP 2,因此HTTP 2目前在實(shí)際使用中,只用于HTTPS協(xié)議場(chǎng)景下。

在最新的Servlet 4.0中,也提供了對(duì)HTTP 2的推送資源(push)特性的支持。

下面通過(guò)一個(gè)示例來(lái)演示如何在HTTP 2下向客戶端推送資源。代碼如程序3-18所示。

程序3-18:TestServlet.java

    @WebServlet(name = "TestServlet", urlPatterns = {"/TestServlet"})
    @ServletSecurity(httpMethodConstraints={
          @HttpMethodConstraint(value="GET", transportGuarantee=CONFIDENTIAL) })
    public class TestServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse res)
              throws IOException, ServletException {
          PushBuilder pushBuilder = req.newPushBuilder().path("my.css");
          pushBuilder.push();
          res.getWriter().println("<html><head><title>HTTP2 Test</title>
            <link rel=\"stylesheet\" href=\"my.css\"></head>
          <body>Hello</body></html>");
        }
    }

程序說(shuō)明:調(diào)用HttpServletRequest的newPushBuilder獲得請(qǐng)求的PushBuilder對(duì)象,并調(diào)用path方法進(jìn)行填充,最后調(diào)用PushBuilder的push方法將資源對(duì)象輸出到客戶端。注意Servlet組件多了注解@ServletSecurity,表示Servlet僅運(yùn)行在HTTPS協(xié)議下且僅支持Get方法。

注意在運(yùn)行程序之前需要首先在服務(wù)器端準(zhǔn)備推送的資源my.css。代碼如程序3-19所示。

程序3-19:my.css

    body { color: blue; }

運(yùn)行程序3-18,由于服務(wù)器端的Push需要運(yùn)行在HTTPS協(xié)議下,NetBeans配置的GlassFish Server 5并沒(méi)有配置相應(yīng)的數(shù)字證書,因此瀏覽器會(huì)彈出如圖3-24所示的警告提示信息。單擊“轉(zhuǎn)到此網(wǎng)頁(yè)(不推薦)”,將得到如圖3-25所示的運(yùn)行結(jié)果。可以看到由于應(yīng)用了服務(wù)器端Push來(lái)的my.css,結(jié)果頁(yè)面中的文本已經(jīng)變成藍(lán)色。

圖3-24 瀏覽器彈出的安全提示

圖3-25 程序3-18運(yùn)行結(jié)果

主站蜘蛛池模板: 怀来县| 灌云县| 社旗县| 江口县| 泰来县| 浦城县| 什邡市| 津南区| 姚安县| 抚顺市| 安陆市| 华坪县| 古田县| 仙居县| 保康县| 壤塘县| 大余县| 湘乡市| 荆门市| 德清县| 赤壁市| 治多县| 旺苍县| 孟连| 开封市| 清苑县| 石门县| 健康| 南陵县| 银川市| 馆陶县| 交城县| 桐柏县| 荥经县| 上虞市| 梁山县| 远安县| 祁东县| 左贡县| 无极县| 石泉县|