[Servlet]HttpServletResponse设置响应标头、缓冲区、语系编码、MIME

1. 设置响应标头:

    1) 标头中的内容也是以键值对的形式出现,一行一个键值对,格式是"键:值列表",标头允许一个键可以有多个值;

    2) 在这里罗列一下常用的HttpServletResponse里关于响应标头设置的方法;

    3) setHeader和addHeader:

         i. void setHeader(String name, String value); // 设置name键的值为value,如果不存在将添加,如果存在则覆盖该键的值

         ii. void addHeader(String name, String value); // 给name键添加一个值value,允许多值添加,如果name键不存在则创建

    4) 如果要添加或者设置的值是整数、日期等特定类型的值则HttpServletResponse也提供了相应类型的方法:

         i. void setIntHeader(String name, int value); // 设置int型键值

         ii. void addIntHeader(String name, int value); // 添加int型值

         iii. void setDateHeader(String name, long date); // 设置Date型值

         iv. void addDateHeader(String name, long date); // 添加Date型值

!!很可惜的是HttpServletResponse只多提供了这4个特殊类型值的标头设置方法,并没有包装其它类型值的标头设置方法,如double等;

    5) 所有标头的设置都必须在响应确认(Commit)前进行,如果在响应确认后在设置则晚了,会被完全忽略,因为响应已经发送给客户端了;

!!那什么是响应确认呢?在接下来的响应缓冲区的介绍中会解释;


2. 设置响应缓冲区:

    1) 响应缓冲区就相当于C语言使用printf等输出函数时使用的缓冲区,所有的printf都不会立马就输出到屏幕上,而是先进入缓冲区,等到要冲刷(flush)时再将缓冲区中的内容显示至屏幕;

    2) 响应缓冲区就是PrintWriter的缓冲区,并不是调用了PrintWriter的各种输出函数后立马就会发送至客户端,而是先将这些内容暂存在响应缓冲区buffer中,一旦符合了某些条件就会将缓冲区中的内容发送至客户端(flush冲刷),具体是什么条件能触发冲刷动作后面会具体罗列;

    3) HttpServletResponse操作响应缓冲区的常用方法罗列:

    // 缓冲区大小的获取和设置

         i. int getBufferSize(); // 返回当前缓冲区的大小,单位是字节,默认情况下是8192,即8KB大小

         ii. void setBufferSize(int size);  // 直接设定缓冲区的大小

!!setBufferSize必须要在getWriter和getOutputStream之前调用,只有这样才会使获得的输出流套用该设置,如果在获得输出刘之后再改动缓冲区大小会直接抛出IllegalStateException异常;

    // 缓冲区内容的清除

         iii. void reset();  // 将缓冲区内的所有内容都清除,包括标头、状态码和body(HTML输出语句等)全部都清除

         iv. void resetBuffer(); // 只清除body,但保留标头和状态码

!!这两个函数必须要在响应提交给客户端(即Commit确认)之前调用,如果已经提交了(缓冲已经空了)再清除内容会直接抛出IllegalStateException;

    // 所以确认是否Commit是关键,使用isCommitted来查看当前是否处于提交状态

         v. boolean isCommitted();  // 查看一轮响应是否提交了,一般要在reset和resetBuffer调用之前先调用这个函数确认响应还未提交

    // 冲刷缓冲区flush

         vi. void flushBuffer();  // 手动强制缓冲区中的内容冲刷至客户端,即将当前缓冲区中已存在的内容全部提交给客户端,该动作会直接出发Commit;

    4) 提交的条件:符合以下6个条件都会触发flush动作提交当前缓冲区中的内容给客户端

         i. 手动调用flushBuffer方法;

         ii. 响应缓冲区的内容已满会自动冲刷;

!!对于一个Response就只有一个缓冲区,如果所有要提交给客户端的内容超过缓冲区大小,那么就会分若干次提交,每次缓冲区满就会冲刷一次;

         iii. Servlet的service方法结束时会强制冲刷一次缓冲区;

         iv. 响应的内容超过标头中指定的body长度的上限时会自动冲刷缓冲区,并关闭输出流(因为标头中规定body只能有这么多,超出部分不予以提交);

!!设置响应body大小上限的方法是:void HttpServletResponse.setContentLength(int len); // 单位是字节,该函数会直接设置Content-Length标头!!

         v. 调用sendRedirect重定向URL时会触发flush;

         vi. 调用sendError向客户端报错时会触发flush;

         vii. 调用了AsyncContext的complete方法时会触发flush;

!!以上三个后面具体会讲;


3. 设置语系和编码:

    1) 使用HttpServletResponse的getWriter返回的PrintWriter输出时默认的字符编码是ISO-8859-1,如果不注意浏览器识别的编码和该默认响应编码不符的话会导致乱码问题;

    2) 这里有三种方法来设定响应内容的编码:设置语系、setCharacterEncoding、setContentType,它们都需要在getWriter之前使用,否则获取的PrintWriter对象不会套用该设置;

    3) Locale——语系:

         i. Locale是Java SE中定义的类,该类包含两个信息,一个是语言,一个是地区,这两者共同组成Locale(语系);

         ii. 而每种语系都对应着一个默认的字符编码,JRE内部维护这一个映射表,表中将每一个语系都映射到一个对应的字符编码方案上,因此一个Locale语系其实包含着三重属性,就是语言、地区还有字符编码;

         iii. Locale的构造器:Locale(String language, String country);

!!国际规范要求language和country都是特定的字符串,都是两个字符,并且language要求为小写,country要求为大写,比如zh-CN,其中语言zh表示中文(zh即”中文“的”中“字”的拼音的头两个字母,而CN则是China的缩写,即中文-大陆地区,还比如zh-TW,即中文-台湾地区;

!!因此可以这样构造:Locale locale = new Locale("zh", "CN");

         iv. 当然Locale类也提供了几个预定义的常静态对象,比如Locale.CHINA就等价于Locale("zh", "CN")等,因此也可以这样创建:Locale locale = new Locale(Locale.CHINA);

    4) Response设置语系来间接设置编码:

         i. HttpServletResponse有一个方法是setLocale:void setLocale(Locale loc); // 该方法会将设定的语系写入响应标头的Accept-Language的值中,Accept-Language就是语系标头

         ii. 由于每种语系都由一个默认的映射表对应着一种编码,因此设置语系的同时也设置了相应内容的编码,比如:resp.setLocale(Local.TAIWAN); // 调用该语句背后也隐含设置了相应编码为BIG5,因为JRE默认的语系-编码映射表中中文-台湾对应的编码是BIG5;

!!那么问题来了,有时候虽然需求的语系是中文-台湾,但是你可能需要用UTF-8来进行编码,那么此时就必须要修改默认的映射表吗?其实不用,只需要在web.xml中注明一下在此Servlet中运用的映射表即可;

         iii. 在web.xml中设置语系映射编码表需要用到的标签:

              a. locale-encoding-mapping-list:语系-编码映射表,里面可以包含多个locale-encoding-mapping标签,一个locale-encoding-mapping标签只能定义一个语系-编码映射对;

              b. 在locale-encoding-mapping标签中定义映射对:locale标签指定语系,语系的格式为"语言_地区",其中语言小写,地区大写,都必须是规定的两个字母,比如zh_CN,接下来的encoding标签中指定编码方案,编码方案还是按照原先的规定书写,比如UTF-8等;

              c. 示例:


	
		zh_CN
		BIG5
	
!这样,setLocale成CHINA后被后的字符编码就成了BIG5了;

    5) 语系标头、获取请求标头中指定的可接受语系信息:

         i. 浏览器在发送请求时可以设置Accept-Language标头,该标头就注明了浏览器可以接受的语系信息,比如Accept-Language:zh-CN就表示接受中国大陆语系;

         ii. Servlet可以使用HttpServletRequest的getLocale从请求标头中获取该语系信息并返回一个Locale对象:Locale getLocale();

         iii. 获取该Locale对象后可以来设置响应HttpServletRespose的语系了(调用response.setLocale即可);

         iv. 当然也可以获得Response对象的当前的Locale信息:Locale HttpServletResponse.getLocale();

    6) 使用setCharacterEncoding设定响应编码:void HttpServletResponse.setCharacterEncoding(String charset);

    7) 使用setContentType设定内容类型的同时顺便设定响应编码:void HttpServletResponse.setContentType(String type);

!!例如response.setContentType("text/html;charset=UTF-8"); // 即设定了内容类型text/html,也设定了相应编码方案为UTF-8;

    8) 三种设定响应编码方法的优先级:

         i. setCharacterEncoding和setContentType都比setLocale强,如果使用了前两者,那么setLocale背后设定的编码就会被忽略,因为语系只是一种表意的东西,并不能决定实际的编码,语系的编码可以随便映射,语系只是一种形式上的参考,实际编码还得看看具体是什么;

         ii. 如果在setContentType中设定了编码,那么就会自动调用setCharacterEncoding将setContentType中设置的编码作为参数传入,因此setContentType的优先级强于setCharacterEncoding;

    9) 设定响应编码时的规范:

         i. 语系只是一种表意的方式,因此有时在标头中写入语系是必要的,但如果你既不想使用默认的映射也不想在web.xml中设定映射编码,则可以用setLocale设定一下语系,然后再用setCharacterEncoding或setContentType修改其中的编码也不失为一种好的解决方案;

         ii. 设定编码精良使用setContenType方法,简洁明了内容多!

         iii. 尽量不要同时使用setCharacterEncoding和setContentType;


!!一个例子:浏览器通过窗体发送中文请求参数,Servlet响应返回中文内容:

form.html



	
		宠物类型大调查
		
	
	
		
姓名:
邮件:
你喜爱的宠物代表:

doPost:

@WebServlet("/pet")
private String htmlTemplate = 
    		"" +
    		" " +
    		"  感谢填写" +
    		" " +
    		" " +
    		"联系人:%s
" + "喜爱的宠物类型" + "
    " + " %s" + "
" + " " + ""; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //doGet(request, response); request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String liType = ""; for (String type: request.getParameterValues("type")) liType += "
  • " + type + "
  • "; String html = String.format(htmlTemplate, request.getParameter("email"), request.getParameter("user"), liType); out.print(html); }
    !!要把form.html放到WebRoot目录下,而在Eclipse工程中是默认创建在WebContent中;


    4. MIME简介:

        1) MIME就是setContentType中的那个"text/html"字段,它表示返回给客户端内容的类型,表示文本类型的html文档,那么MIME用来干什么呢?

        2) MIME全称是:Multipurpose Internet Mail Extensions,即多用途电子邮件类型扩展,它表示在网络中传输的资源的类型;

        3) MIME的作用:

             i. 最早应用于电子邮件的附件,通常电子邮件都可以添加附件一起传送,那么问题来了,附件到了以后该如何打开呢?更精确地讲应该用什么程序来打开这个附件呢?那么就要用MIME来指示了,如果MIME是application/pdf,那么浏览器就知道应该用Adobe Reader来打开,也就是说MIME是给浏览器看的,浏览器根据MIME来调取合适的应用程序来打开附件;

             ii. 直到现在,MIME仍然用于表示文件类型,只不过已经不仅仅用于标识电子邮件的附件了,现在也可以用来标识任何用HTTP协议传输的资源了,所以叫做Multipurpose,即多用途,并且是电子邮件的扩展!

        4) MIME的书写格式:大类别/具体的种类,比如text/html,大类别就是文本文件text,具体种类是超文本标记文档

    !!注意:种类和文件后缀是两码事!text/html中的html是种类而不是文件后缀,种类指示表示文件中的内容的类型,比如我一个以txt为后缀的文档,但是里面的内容确实用超文本标记语言写的,即使你文件后缀是txt,但是浏览器看到你的MIME是text/html则还是会用浏览器而不是notepad来打开这个文件!

    !!所以,MIME才是真正决定打开方式的钥匙,而文件后缀只是一种点缀,不起到任何作用,仅仅是形式上更好的表示资源的类型,完全可以使用一个和内容类型不符的后缀来隐藏该资源的真正用途;

    !!编程时应该做到后缀和MIME相匹配,除非你有一些不良的意图!

        5) 常用的MIME类型:

    .html:text/html

    .xml:text/xml

    .txt:text/plain

    .pdf:application/pdf

    .tar:application/x-tar

    .gif:image/gif

    .png:image/png

    .mpg:audio/mpeg

    !!可以看到都还是很有规律的

    你可能感兴趣的:(Java,Web,Servlet)