客户请求的处理:Http请求报头
创建高效servlet的关键之一,就是要了解如何操纵超文本传输协议(HypeText TransferProtocol, HTTP)。
HTTP请求报头不同于前一章的表单数据。表单数据直接来源于用户的输入,对于GET请求,这些数据是URL的一部分,对于POST请求,这些数据在单独的行中。相应的,请求报头由浏览器间接设定,并紧跟在初始的GET和POST请求行之后发送。
请求报头的读取:
HttpServletRequest的getHeader方法,使用报头名称为参数。有返回String,没有返回null。
报头名称对大小写不敏感。
尽管getHeader是读取报头的通用方式,但由于几种报头的应用太普遍,故而HttpServletRequest为它们提供了专门的访问方法:
getCookies
getCookies返回Cookie报头的内容,这些内容经分析后存储在由Cookie对象构成的数组中。
getAuthType和getRemoteUser
getAuthType和getRemoteUser方法对Authorization报头进行拆分,分解成它的各个构成部分。
getContentLength
getContentLength方法返回Content-Length报头的值(int)
getContentType
getContentType返回Content-Type报头的值(string)
getDateHeader和getIntHeader
getDateHeader和getIntHeader饭别读取指定的报头,然后分别将它们转换成Date和int值
getHeaderNames
可以用getHeaderNames方法得到一个Enumeration,枚举当前特定请求中所有的报头名称
getHeaders
大多数情况下,每个报头名称在请求中只出现一次。然而报头偶尔也可能出现多次,每次出现列出各自的值。Accept-Language就是一例子。您可以使用getHeaders获取一个Enumeration,枚举报头每次出现所对应的值
除了查找请求的报头之外,您还可以获取主请求行自身的信息,同样是使用HttpServletRequest提供的方法
getMethod
返回主请求方法
getRequestURI
返回URL中主机和端口之后,但在表单数据之前的部分
getQueryString
返回表单数据
getProtocol
getProtocol方法返回请求行的第三部分,一般为HTTP/1.0或者HTTP/1.1。
了解Http请求报头:
Accept:浏览器能处理的MIME类型。
Accept-Charset: 浏览器使用的字符串
Accept-Encoding
Accept-Language:浏览器使用的语言,如en
Authorization:访问需要用户信息的web页面的时候,用这个报头标识自己身份
Connect:标识浏览器能否处理持续性连接。Keep-Alive表示应该使用持续性连接,close表示使用旧式连接
Content-Length:
Cookie:
Host:URL中的主机名和端口号
if-Modified-Since:这个报头标明,仅当页面在指定日期之后修改的情况下,客户程序才能访问页面。如果没有更新的结果,则返回403.这个选项十分有用,因为使用它,浏览器可以缓存文档,只有他们发生更改时才通过网络重新载入。但是servlet不需要直接处理这个报头。取而代之,他们应该事先getLastModified方法,让系统自动处理修改日期。
使用getLastModified的例子:
package com.zhen.test.o2; import com.zhen.util.ServletUtilities; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter; /**
* Created by zhen on 2017-10-10.
* Example using servlet initialization and the getLastModified method
*/
public class LottyNumbers extends HttpServlet{
private long modTime;
private int[] numbers = new int[10]; /**
* The init method is called only when the servlet is first loaded, before the first request is processed.
* @throws ServletException
*/
public void init() throws ServletException {
// Round to nearest second (i.e, 1000 milliseconds)
modTime = System.currentTimeMillis()/1000*1000;
for(int i=0; i<numbers.length; i++){
numbers[i] = randomNum();
}
} /** Return the list of numbers that init computed. */
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
String title = "Your Lottery Numbers";
out.println(ServletUtilities.headWithTitle(title) +
"<BODY BGCOLOR=\"#FDF5F6\">\n" +
"<H1 ALIGN=CENTER>" + title + "</H1>\n" +
"<B> Based ypon extensive research of astro-illogical statistical claptrap, we have chosen the " +
numbers.length + "best lottery numbers for you. </B>" +
"<OL>"
);
for(int i=0; i<numbers.length; i++) {
out.println(" <LI>" + numbers[i] + "</LI>");
}
out.println("</OL>" +
"<BODY></HTML>");
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
} /**
* The standard service method compares this date against any date specified in the If-Modified-Since request header.
* If the getLastModified date is later or if there is no
* If-Modified-Since header, the doGet method is called
* normally. But if the getLastModified date is the same or earlier, the service method sends back a 304 (Not Modified)
* response and does not call doGet. The browser should use it cached version of the page in such a case.
* @param req
* @return
*/
@Override
public long getLastModified(HttpServletRequest req) {
return (modTime);
} // A random int from 0 to 99
private int randomNum() {
return (int)(Math.random() * 100);
}
}
if-Modified-Since:
这个报头和if-Modified-Since正好相反:它规定仅当文档比指定的日期要旧时,操作才需要继续
Referer:标明web页面的URL。用于追踪请求来源
User-Agent:标识生成请求的浏览器或者其他客户程序
发送压缩页面
gzip文本压缩方案能极大减少HTML(或纯文本)页面的大小。大多数最近的浏览器都直达如何处理gzip压缩后的内容,所以,服务器对文档进行压缩,使得传输更小。
servlet首先检查Accept-Encoding报头,检查它是否包含有关gzip的项。如果支持,它使用PrintWriter封装GZIPOutputStream,并指定Content-Encoding响应报头值为gzip。如果不支持gzip,则使用正常的PrintWriter。浏览器禁用压缩可以在URL尾部加上?disableGzip
使用gzip压缩返回页面的例子:
package com.zhen.test.o3; import com.zhen.util.GzipUtilities;
import com.zhen.util.ServletUtilities; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter; /**
* Created by zhen on 2017-11-05.
*/
public class LongServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("utf-8");
PrintWriter out;
if(GzipUtilities.isGzipSupported(req) && !GzipUtilities.isGzipDisabled(req)){
out = GzipUtilities.getGzipWriter(resp);
}else{
out = resp.getWriter();
}
String title = "Long Page";
out.println(ServletUtilities.headWithTitle(title) + "" +
"<body bgcolor=\"#FDF5E6\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n");
String line = "Blah, blah, blah, blah, blah. Yadda, yadda, yadda, yadda, yadda.";
for(int i = 0; i <10000; i++){
out.println(line);
}
out.println("</body></html>");
out.close();
}
} package com.zhen.util; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream; /**
* Created by zhen on 2017-11-05.
*/
public class GzipUtilities { public static boolean isGzipSupported(HttpServletRequest request) {
String encodings = request.getHeader("Accept-Encoding");
return (encodings != null)
&& (encodings.indexOf("gzip") != -1);
} public static boolean isGzipDisabled(HttpServletRequest request) {
String flag = request.getParameter("disableGzip");
return (flag != null)
&& (!flag.equalsIgnoreCase("false"));
} public static PrintWriter getGzipWriter(HttpServletResponse response) throws IOException{
response.setHeader("Content-Encoding","gzip");
return new PrintWriter(new GZIPOutputStream(response.getOutputStream()));
}
}
区分不同的浏览器类型:
User-Agent报头标识发出的请求的具体浏览器。
注意细节:
1、仅仅在必需的时候才会使用User-Agent
2、检查是否为null
3、区分Netscape和Internet Explorer,要检查“MSIE”,而并非“Mozilla”.Netscape和Internet Explorer都在该报头的开始处列出“Mozilla”,尽管Mozilla是类Godzilla的Netscape吉祥物。这个特征是为了与javascript兼容
4、注意,该报头可以造假
根据客户的到达方式定制页面
Referer报头支出,用户单击链接到达当前页面时所处页面的位置。如果用户直接输入页面的地址。那么浏览器就不会发送Referer。
通过这个报头,可以做到:
1、创建一个工作网站,承袭链接到它的相关网站的外观感觉。
2、根据链接来自防火墙内部还是外部,更改页面的内容
3、提供相应的链接,使用户能够返回之前的页面
4、跟踪标题广告的有效性,或者记录显示您广告不同网站的点击率
标砖CGI变量的访问
存在一个有些混杂的集合,存储各种与当前请求相关的信息。有些基于http请求中的行和报头,其他来自于socket本身,还有一些取自服务器的安装参数。
servlet中CGI变量的等价物:
1、AUTH_TYPE
若提供了Authoriztion报头。这个变量给出指定的模式(basic或digest)。用request.getAuthType()来访问它。
2、CONTENT_LENGTH
调用request.getContentLength()即可访问
3、CONTENT_TYPE
调用request.getContentType()即可访问
4、DOCUMENT_ROOT
DOCUMENT_ROOT变量指定与URL http://host/对应的实际目录。用getServletContext()。getRealPath("/")对它进行访问
5、HTTP_XXX_YYY
Cookie报头成为HTTP_COOKIE, User-agent成为HTTP_USER_AGENT,Referer称为HTTP_REFERER,依次类推。servlet应该只是用request.getHeaderNames或者快捷方法
6、PATH_INFO
路径信息可以作为常规表单数据的一部分进行发送,之后用getServletContext().getRealPath进行转换。是用request.getPathInfo()访问PATH_INFO的值
7、PATH_TRANSLATED
PATH_TRANSLATED给出映射到服务器实际路径的路径信息。通过request.getPathTranslated()访问。
8、QUERY_STRING
对于get请求,这个变量以单字符串的形式给出附加的数据,这些数据依旧保持URL编码之后的状态,一般使用getParameter访问。如果您真的需要原始数据,可以通过request.getQueryString()得到它
9、REMOTE_ADDR
这个变量指出发出请求的客户机的IP地址,调用request.getRemoteAddr()对它进行访问
10、REMOTE_HOST
给出发出请求的客户机的完全限定域名,如果不能确定域名,返回ip地址。使用request.getRemoteHost()访问
11、REMOTE_USER
如果提供了Authorization报头,并经过服务器自身的解码,那么REMOTE_USER变量给出用户相关的部分。用request.getRemoteUser访问它
12、REQUEST_METHOD
通过request.getMethod()访问这个变量
13、SCRIPT_NAME
这个变量给出servlet的路径,相对于服务器的根目录。可以通过request.getServletPath()访问
14、SERVER_NAME
服务器计算机的主机名。可以用request.getServerName访问
15、SERVER_PORT
存储服务器正在侦听的端口。技术上等价于String.valueOf(request.getServerPort())
16、SERVER_PROTOCOL
标明在请求行中使用的协议名称和版本。用request.getProtocol进行访问
17、SERVER_SOFTWARE
给出web服务器的标识信息。通过getServletContext().getServletInfo()进行访问。