系列文章目录
Java Servlet 学习笔记(版本3.1)(1)
Java Servlet 学习笔记(版本3.1)(2)
Java Servlet 学习笔记(版本3.1)(3)
Java Servlet 学习笔记(版本3.1)(4)
Java Servlet 学习笔记(版本3.1)(5)
文章目录
前言
在前一章中我们引入了Servlet以及Servlet容器等概念,通过Servlet服务器(也可以成为Web服务器)可以将客户端的请求发送到容器当中的Servlet实例,通过Servlet实例对应的接口针对这个请求进行处理,处理完成后再将结果返回给容器,容器再发送给客户端,完成一次网络请求。这里的请求对象抽象为Request
,而返回对象抽象为Response
。本章来详细学习一下Request
接口
提示:以下是本篇文章正文内容,下面案例可供参考
请求对象(Request
)封装了来自客户端请求的所有信息。 在HTTP协议中,从客户端传输到服务器的请求的完整信息就包含在HTTP头信息(headers
)和请求的消息正文(body
)中。对应的接口为javax.servlet.ServletRequest
.
ServletRequest defines an object to provide client request information to a servlet. The servlet container creates a ServletRequest object and passes it as an argument to the servlet’s service method.
A ServletRequest object provides data including parameter name and values, attributes, and an input stream. Interfaces that extend ServletRequest can provide additional protocol-specific data (for example, HTTP data is provided by javax.servlet.http.HttpServletRequest.
为了以下能够清晰理解,创建一个静态web简易程序, 使用的Servlet容器为tomcat,版本为8.0(必须明确Tomcat仅为Servlet容器的一个实现,还有其他的Servelt容器)。
创建一个webapp文件夹,路径为D:\app\webapp
。并在其中创建一个WEB-INF的文件夹
创建一个Servlet类
import javax.servlet.ServletConfig;
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;
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
System.out.println("-------------------I am DemoServlet----------------");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html><html>");
out.println("<head>");
out.println("<meta charset=\"UTF-8\" />");
String title = "helloworld";
out.println("<title>" + title + "</title>");
out.println("</head>");
out.println("<body bgcolor=\"white\">");
out.println("<h1>" + title + "</h1>");
out.println("</body>");
out.println("</html>");
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("-------------------DemoServlet init----------------");
}
}
编译成class文件并拷贝到目录D:\app\webapp\WEB-INF\classes
下
在WEB-INF
目录下创建一个web.xml文件,其中的信息如下(这里将Servlet类配置到tomcat容器当中)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<servlet>
<servlet-name>aServlet</servlet-name>
<servlet-class>DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>aServlet</servlet-name>
<url-pattern>/demo/*</url-pattern>
</servlet-mapping>
</web-app>
为了让tomcat明确加载的路径,在tomcat的conf\Catalina\localhost
路径下添加配置文件webapp.xml
(文件名称任意,但是其中内容不能修改)。
<?xml version='1.0' encoding='utf-8'?>
<Context docBase="D:/app/webapp" path="/webapp" reloadable="true"></Context>
启动tomcat,启动完成之后,在浏览器页面发起如下请求
http://localhost:8080/webapp/demo/1
此时页面显示helloworld
字体,而且控制台打印如下内容
-------------------DemoServlet init----------------
-------------------I am DemoServlet----------------
初始化是在第一次请求之后 而不是在容器启动时 说明tomcat默认对Servlet为懒加载
一、HTTP协议参数
Servlet的请求参数是客户端作为其请求的一部分发送到Servlet容器的字符串。 当请求是HttpServletRequest对象,并且满足“当参数可用时”中列出的条件时,容器将根据URI查询字符串和请求数据来填充请求参数。
参数存储为一组键值对。 对于任何给定的参数名称,可以存在多个参数值。 ServletRequest接口的以下方法可用于访问参数:
/**
* Returns the value of a request parameter as a <code>String</code>, or
* <code>null</code> if the parameter does not exist. Request parameters are
* extra information sent with the request. For HTTP servlets, parameters
* are contained in the query string or posted form data.
* <p>
* You should only use this method when you are sure the parameter has only
* one value. If the parameter might have more than one value, use
* {@link #getParameterValues}.
* <p>
* If you use this method with a multivalued parameter, the value returned
* is equal to the first value in the array returned by
* <code>getParameterValues</code>.
* <p>
* If the parameter data was sent in the request body, such as occurs with
* an HTTP POST request, then reading the body directly via
* {@link #getInputStream} or {@link #getReader} can interfere with the
* execution of this method.
*
* @param name
* a <code>String</code> specifying the name of the parameter
* @return a <code>String</code> representing the single value of the
* parameter
* @see #getParameterValues
*/
public String getParameter(String name);
/**
* Returns an <code>Enumeration</code> of <code>String</code> objects
* containing the names of the parameters contained in this request. If the
* request has no parameters, the method returns an empty
* <code>Enumeration</code>.
*
* @return an <code>Enumeration</code> of <code>String</code> objects, each
* <code>String</code> containing the name of a request parameter;
* or an empty <code>Enumeration</code> if the request has no
* parameters
*/
public Enumeration<String> getParameterNames();
/**
* Returns an array of <code>String</code> objects containing all of the
* values the given request parameter has, or <code>null</code> if the
* parameter does not exist.
* <p>
* If the parameter has a single value, the array has a length of 1.
*
* @param name
* a <code>String</code> containing the name of the parameter
* whose value is requested
* @return an array of <code>String</code> objects containing the parameter's
* values
* @see #getParameter
*/
public String[] getParameterValues(String name);
/**
* Returns a java.util.Map of the parameters of this request. Request
* parameters are extra information sent with the request. For HTTP
* servlets, parameters are contained in the query string or posted form
* data.
*
* @return an immutable java.util.Map containing parameter names as keys and
* parameter values as map values. The keys in the parameter map are
* of type String. The values in the parameter map are of type
* String array.
*/
public Map<String, String[]> getParameterMap();
创建一个页面index.html
<!DOCTYPE HTML><html lang="en"><head>
<meta charset="UTF-8">
<title>Apache Tomcat Examples</title>
</head>
<body>
<form action="http://localhost:8080/webapp/demo/1">
First name:<br>
<input type="text" name="firstname" value="Mickey">
<br>
Last name:<br>
<input type="text" name="lastname" value="Mouse">
<br>
I have a bike:
<input type="checkbox" name="vehicle" value="Bike" checked="checked" />
<br />
I have a car:
<input type="checkbox" name="vehicle" value="Car" />
<br />
I have an airplane:
<input type="checkbox" name="vehicle" value="Airplane" />
<br /><br />
<br>
<input type="submit" value="Submit">
</form>
<p>如果您点击提交,表单数据会被发送到对应Servlet请求。</p>
</body>
</html>
页面显示如下
修改Servlet实现
import javax.servlet.ServletConfig;
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;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
System.out.println("-------------------I am DemoServlet----------------");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html><html>");
out.println("<head>");
out.println("<meta charset=\"UTF-8\" />");
out.println("</head>");
out.println("<body bgcolor=\"white\">");
out.println("Hello World!");
System.out.println("request.getParameter(\"firstname\") = " + request.getParameter("firstname"));
System.out.println("request.getParameter(\"lastname\") = " + request.getParameter("lastname"));
System.out.println("request.getParameter(\"vehicle\") = " + request.getParameter("vehicle"));
String[] vehicles = request.getParameterValues("vehicle");
System.out.println("request.getParameterValues(\"vehicle\") = " + Arrays.toString(vehicles));
System.out.println("\n-------------------------parameterNames--------------------------------");
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
System.out.println(parameterNames.nextElement());
}
System.out.println("\n-------------------------getParameterMap--------------------------------");
Map<String, String[]> parameterMap = request.getParameterMap();
Iterator<Map.Entry<String, String[]>> iterator = parameterMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String[]> next = iterator.next();
System.out.println(next.getKey() + " = " + Arrays.toString(next.getValue()));
}
out.println("</body>");
out.println("</html>");
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("-------------------DemoServlet init----------------");
}
}
点击上面的页面发起请求(多选框选前面两个),控制台打印的结果如下
getParameterValues
方法以字符串数组返回指定参数名称的所有参数值。比如在前台多选框是一个参数名称,对应的参数值可能为多个,调用此方法会将选中的值都返回。但是如果是调用getParameter
方法则只会将第一个值返回(比如上面针对vehicle参数的请求只能返回第一个值Bike)。而通过getParameterMap
方法返回一个不可变的java.util.Map
对象,请求参数名称作为key,而参数值作为value,value类型不是String,而是字符串数组(所以此时vehicle参数的值都可以获取到)。
来自查询字符串和POST表格请求的数据被汇总到请求参数集中。 查询字符串数据在POST表格请求数据之前显示。 例如,如果使用查询字符串a = hello和表单提交参数a = goodbye&a = world发出请求,则结果参数集将按a =(hello,goodbye,world)排序。比如在上面的案例中在请求中添加查询参数vehicle=Boat。
修改页面的action如下所示
<form action="http://localhost:8080/webapp/demo/1?vehicle=Boat" method="post">
同时覆盖DemoServlet
的doPost
方法
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
请求结果如下,可见vehicle=Boat查询参数起了作用。
这些API不会公开属于GET请求(由HTTP 1.1定义)的路径参数。 必须从getRequestURI方法或getPathInfo方法返回的String值中解析它们。
System.out.println("request.getRequestURI = " + request.getRequestURI());
System.out.println("request.getPathInfo = " + request.getPathInfo());
上面我们提到了当参数可用时,那么什么是参数可用条件呢?以下是POST请求表格数据填充到参数集之前必须满足的条件:
- 请求是符合HTTP或HTTPS协议
- HTTP请求类型为POST
- 内容类型为application/x-www-form-urlencoded
- Servlet已对请求对象上的任何getParameter方法族进行了初始调用
如果不满足条件并且POST表格数据未包含在参数集中,则POST数据仍必须可通过请求对象的输入流(getInputStream
)提供给Servlet。 如果满足条件,则将不再可以直接从请求对象的输入流中读取表单数据。
二、文件上传
Servlet容器允许文件作为multipart/formdata
格式上传。
Servlet容器提供multipart/form-data
类型数据的处理如果满足以下两个条件的任何一个:
- 处理对应的请求的Servlet包含有注解
@MultipartConfig
。 - 在声明时包含有
multipart-config
元素
如何使multipart / form-data类型的请求中的数据可用取决于servlet容器是否提供multipart / form-data处理。如果容器支持,那么这些数据通过以下的方法处理
这些方法在javax.servlet.http.HttpServletRequest#getParts
接口中定义
/**
* Return a collection of all uploaded Parts.
*
* @return A collection of all uploaded Parts.
* @throws IOException
* if an I/O error occurs
* @throws IllegalStateException
* if size limits are exceeded or no multipart configuration is
* provided
* @throws ServletException
* if the request is not multipart/form-data
* @since Servlet 3.0
*/
public Collection<Part> getParts() throws IOException,
ServletException;
/**
* Gets the named Part or null if the Part does not exist. Triggers upload
* of all Parts.
*
* @param name The name of the Part to obtain
*
* @return The named Part or null if the Part does not exist
* @throws IOException
* if an I/O error occurs
* @throws IllegalStateException
* if size limits are exceeded
* @throws ServletException
* if the request is not multipart/form-data
* @since Servlet 3.0
*/
public Part getPart(String name) throws IOException,
ServletException;
其中返回的javax.servlet.http.Part
类型包含以下这些方法。可以获取头信息、内容类型、并且通过getInputStream
获取到流数据。
编写页面(需要在action当中配置enctype="multipart/form-data"
)
<!DOCTYPE HTML><html lang="en"><head>
<meta charset="UTF-8">
<title>Apache Tomcat Examples</title>
</head>
<body>
<form action="http://localhost:8080/webapp/demo/1" enctype="multipart/form-data" method="post" >
<br />
<input name="pic" type="file" accept="image/gif, image/jpeg"/>
<br />
<br>
<input type="submit" value="Submit">
</form>
<p>如果您点击提交,表单数据会被发送到对应Servlet请求。</p>
</body>
</html>
Servlet编码如下(需要添加注解@MultipartConfig
)
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
@MultipartConfig
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
System.out.println("-------------------I am DemoServlet----------------");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html><html>");
out.println("<head>");
out.println("<meta charset=\"UTF-8\" />");
out.println("</head>");
out.println("<body bgcolor=\"white\">");
out.println("Hello World!");
out.println("</body>");
out.println("</html>");
Collection<Part> parts = request.getParts();
for (Part part : parts) {
System.out.println("name = " + part.getName());
System.out.println("ContentType = " + part.getContentType());
System.out.println("headerNames = " + Arrays.toString(part.getHeaderNames().toArray()));
System.out.println("size = " + part.getSize());
System.out.println("inputStream = " + part.getInputStream());
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("-------------------DemoServlet init----------------");
}
}
启动tomcat,并上传文件提交,控制台打印如下:
name = pic
ContentType = image/jpeg
headerNames = [content-disposition, content-type]
size = 101829
inputStream = java.io.FileInputStream@235a9a99
由此可见从服务器端可以获取到文件信息了(尤其是数据流)。
如果Servlet容器不提供
multi-part/form-data
数据的处理,那么通过HttpServletReuqest.getInputStream可以获取到数据。
三、属性
属性是与请求关联的对象。 可以由容器设置属性以表示原本无法通过API表示的信息,或者可以由Servlet设置属性以将信息传递给另一个Servlet(通过RequestDispatcher)。 使用ServletRequest接口的以下方法访问属性。
/**
* Returns the value of the named attribute as an <code>Object</code>, or
* <code>null</code> if no attribute of the given name exists.
* <p>
* Attributes can be set two ways. The servlet container may set attributes
* to make available custom information about a request. For example, for
* requests made using HTTPS, the attribute
* <code>javax.servlet.request.X509Certificate</code> can be used to
* retrieve information on the certificate of the client. Attributes can
* also be set programmatically using {@link ServletRequest#setAttribute}.
* This allows information to be embedded into a request before a
* {@link RequestDispatcher} call.
* <p>
* Attribute names should follow the same conventions as package names.
* Names beginning with <code>java.*</code> and <code>javax.*</code> are
* reserved for use by the Servlet specification. Names beginning with
* <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
* <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
*
* @param name
* a <code>String</code> specifying the name of the attribute
* @return an <code>Object</code> containing the value of the attribute, or
* <code>null</code> if the attribute does not exist
*/
public Object getAttribute(String name);
/**
* Returns an <code>Enumeration</code> containing the names of the
* attributes available to this request. This method returns an empty
* <code>Enumeration</code> if the request has no attributes available to
* it.
*
* @return an <code>Enumeration</code> of strings containing the names of the
* request's attributes
*/
public Enumeration<String> getAttributeNames();
/**
* Stores an attribute in this request. Attributes are reset between
* requests. This method is most often used in conjunction with
* {@link RequestDispatcher}.
* <p>
* Attribute names should follow the same conventions as package names.
* Names beginning with <code>java.*</code> and <code>javax.*</code> are
* reserved for use by the Servlet specification. Names beginning with
* <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
* <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
* <br>
* If the object passed in is null, the effect is the same as calling
* {@link #removeAttribute}. <br>
* It is warned that when the request is dispatched from the servlet resides
* in a different web application by <code>RequestDispatcher</code>, the
* object set by this method may not be correctly retrieved in the caller
* servlet.
*
* @param name
* a <code>String</code> specifying the name of the attribute
* @param o
* the <code>Object</code> to be stored
*/
public void setAttribute(String name, Object o);
其中参数名称和值是一一对应的关系。另外对于参数的名称也需要注意,比如以java或者javax开头的名称是被规范保留的,另外sun.
、com.sun.
、oracle
、com.oracle
这些也是保留的,毕竟Java是人家的嘛。建议根据Java编程语言规范1提出的用于包命名的反向域名约定来命名放置在属性集中的所有属性。
比如在doPost中设置参数
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("username", "Tomcat");
req.setAttribute("password", 123456);
this.doGet(req, resp);
}
然后在doGet方法中获取参数
Enumeration<String> attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String nextElement = attributeNames.nextElement();
System.out.println(nextElement + " = " + request.getAttribute(nextElement));
}
发起请求此时在控制台包含如下信息
password = 123456
username = Tomcat
在Servlet请求过程中通过这样的方式传递参数是非常合适的
四、头信息
Servlet可以通过以下HttpServletRequest接口的方法访问HTTP请求的头信息
/**
* Returns the value of the specified request header as a
* <code>String</code>. If the request did not include a header of the
* specified name, this method returns <code>null</code>. If there are
* multiple headers with the same name, this method returns the first head
* in the request. The header name is case insensitive. You can use this
* method with any request header.
*
* @param name
* a <code>String</code> specifying the header name
* @return a <code>String</code> containing the value of the requested
* header, or <code>null</code> if the request does not have a
* header of that name
*/
public String getHeader(String name);
/**
* Returns all the values of the specified request header as an
* <code>Enumeration</code> of <code>String</code> objects.
* <p>
* Some headers, such as <code>Accept-Language</code> can be sent by clients
* as several headers each with a different value rather than sending the
* header as a comma separated list.
* <p>
* If the request did not include any headers of the specified name, this
* method returns an empty <code>Enumeration</code>. The header name is case
* insensitive. You can use this method with any request header.
*
* @param name
* a <code>String</code> specifying the header name
* @return an <code>Enumeration</code> containing the values of the requested
* header. If the request does not have any headers of that name
* return an empty enumeration. If the container does not allow
* access to header information, return null
*/
public Enumeration<String> getHeaders(String name);
/**
* Returns an enumeration of all the header names this request contains. If
* the request has no headers, this method returns an empty enumeration.
* <p>
* Some servlet containers do not allow servlets to access headers using
* this method, in which case this method returns <code>null</code>
*
* @return an enumeration of all the header names sent with this request; if
* the request has no headers, an empty enumeration; if the servlet
* container does not allow servlets to use this method,
* <code>null</code>
*/
public Enumeration<String> getHeaderNames();
与getParameter方法极为相似,如果请求的某个名称的头信息是一个数组,则只会返回第一个元素,此时需要使用以下方法
/**
* Returns all the values of the specified request header as an
* <code>Enumeration</code> of <code>String</code> objects.
* <p>
* Some headers, such as <code>Accept-Language</code> can be sent by clients
* as several headers each with a different value rather than sending the
* header as a comma separated list.
* <p>
* If the request did not include any headers of the specified name, this
* method returns an empty <code>Enumeration</code>. The header name is case
* insensitive. You can use this method with any request header.
*
* @param name
* a <code>String</code> specifying the header name
* @return an <code>Enumeration</code> containing the values of the requested
* header. If the request does not have any headers of that name
* return an empty enumeration. If the container does not allow
* access to header information, return null
*/
public Enumeration<String> getHeaders(String name);
另外获取的头信息虽然是String,但可能表达的其实是Integer或者Date类型的信息,可以通过HttpServletRequest以下的接口获取,但是需要注意的是如果对应的参数无法转换为Int或者Date,那么就会抛出NumberFormatException
或IllegalArgumentException
。
/**
* Returns the value of the specified request header as an <code>int</code>.
* If the request does not have a header of the specified name, this method
* returns -1. If the header cannot be converted to an integer, this method
* throws a <code>NumberFormatException</code>.
* <p>
* The header name is case insensitive.
*
* @param name
* a <code>String</code> specifying the name of a request header
* @return an integer expressing the value of the request header or -1 if the
* request doesn't have a header of this name
* @exception NumberFormatException
* If the header value can't be converted to an
* <code>int</code>
*/
public int getIntHeader(String name);
/**
* Returns the value of the specified request header as a <code>long</code>
* value that represents a <code>Date</code> object. Use this method with
* headers that contain dates, such as <code>If-Modified-Since</code>.
* <p>
* The date is returned as the number of milliseconds since January 1, 1970
* GMT. The header name is case insensitive.
* <p>
* If the request did not have a header of the specified name, this method
* returns -1. If the header can't be converted to a date, the method throws
* an <code>IllegalArgumentException</code>.
*
* @param name
* a <code>String</code> specifying the name of the header
* @return a <code>long</code> value representing the date specified in the
* header expressed as the number of milliseconds since January 1,
* 1970 GMT, or -1 if the named header was not included with the
* request
* @exception IllegalArgumentException
* If the header value can't be converted to a date
*/
public long getDateHeader(String name);
修改Servlet方法获取头信息
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("username", "Tomcat");
req.setAttribute("password", 123456);
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String element = headerNames.nextElement();
String header = req.getHeader(element);
System.out.println(element + " = " + header);
}
this.doGet(req, resp);
}
此时控制台包含如下信息
host = localhost:8080
connection = keep-alive
content-length = 102010
cache-control = max-age=0
upgrade-insecure-requests = 1
origin = null
content-type = multipart/form-data; boundary=----WebKitFormBoundaryAS7GjjcKzckDbtxw
user-agent = Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36
sec-fetch-dest = document
accept = text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site = cross-site
sec-fetch-mode = navigate
sec-fetch-user = ?1
accept-encoding = gzip, deflate, br
accept-language = zh-CN,zh;q=0.9
五、请求路径信息
引导Servlet服务请求的请求路径由许多重要部分组成。 以下元素是从请求URI路径获得的,并通过请求对象公开
- Context Path:这个参数与ServletContext有关,如果没有指定,为空字符串,如果有特别指定,一般都是以
/
字符串开头,但是不能以/
字符串结束。比如上面案例当中我们在tomcat里面的配置文件中的path="/webapp"
就是定义Context Path信息的。
对应的获取方法为javax.servlet.http.HttpServletRequest#getContextPath
.
/**
* Returns the portion of the request URI that indicates the context of the
* request. The context path always comes first in a request URI. The path
* starts with a "/" character but does not end with a "/" character. For
* servlets in the default (root) context, this method returns "". The
* container does not decode this string.
*
* @return a <code>String</code> specifying the portion of the request URI
* that indicates the context of the request
*/
public String getContextPath();
- Servlet Path:这个参数与Servlet有关。在Servlet容器中包含有很多的Servlet,每个Servlet都与不同的请求路径匹配。在上面案例当中我们有如下的配置
<servlet-mapping>
<servlet-name>aServlet</servlet-name>
<url-pattern>/demo/*</url-pattern>
</servlet-mapping>
首先这里的*
指的是匹配所有。上面的含义就是名称为aServlet
对应的Servlet Path为/demo/*
.
对应的获取方法为javax.servlet.http.HttpServletRequest#getServletPath
.
/**
* Returns the part of this request's URL that calls the servlet. This path
* starts with a "/" character and includes either the servlet name or a
* path to the servlet, but does not include any extra path information or a
* query string. Same as the value of the CGI variable SCRIPT_NAME.
* <p>
* This method will return an empty string ("") if the servlet used to
* process this request was matched using the "/*" pattern.
*
* @return a <code>String</code> containing the name or path of the servlet
* being called, as specified in the request URL, decoded, or an
* empty string if the servlet used to process the request is
* matched using the "/*" pattern.
*/
public String getServletPath();
- PathInfo:在完整路径中除了Context Path和Servlet Path之后还有一些其他部分,这些可以认为是PathInfo。
对应的获取方法为javax.servlet.http.HttpServletRequest#getContextPath
.
/**
* Returns any extra path information associated with the URL the client
* sent when it made this request. The extra path information follows the
* servlet path but precedes the query string and will start with a "/"
* character.
* <p>
* This method returns <code>null</code> if there was no extra path
* information.
* <p>
* Same as the value of the CGI variable PATH_INFO.
*
* @return a <code>String</code>, decoded by the web container, specifying
* extra path information that comes after the servlet path but
* before the query string in the request URL; or <code>null</code>
* if the URL does not have any extra path information
*/
public String getPathInfo();
需要注意的重要一点是,除了请求URI和路径部分之间的URL编码差异之外,以下等式始终成立:requestURI = contextPath + servletPath + pathInfo
在以上的案例当中添加如下代码
System.out.println("ContextPath = " + req.getContextPath());
System.out.println("ServletPath = " + req.getServletPath());
System.out.println("PathInfo = " + req.getPathInfo());
此时的完整请求路径为:http://localhost:8080/webapp/demo/1
控制台包含如下信息
ContextPath = /webapp
ServletPath = /demo
PathInfo = /1
host = localhost:8080
BTW,完整请求路径最前面的http是网络协议,英文名scheme.
六、路径转换方法
API中有两种便捷方法,它们使开发人员可以获得与特定路径等效的文件系统路径。 这些方法是
javax.servlet.ServletContext#getRealPath
/**
* Returns a <code>String</code> containing the real path for a given
* virtual path. For example, the path "/index.html" returns the absolute
* file path on the server's filesystem would be served by a request for
* "http://host/contextPath/index.html", where contextPath is the context
* path of this ServletContext..
* <p>
* The real path returned will be in a form appropriate to the computer and
* operating system on which the servlet container is running, including the
* proper path separators. This method returns <code>null</code> if the
* servlet container cannot translate the virtual path to a real path for
* any reason (such as when the content is being made available from a
* <code>.war</code> archive).
*
* @param path
* a <code>String</code> specifying a virtual path
* @return a <code>String</code> specifying the real path, or null if the
* translation cannot be performed
*/
public String getRealPath(String path);
javax.servlet.http.HttpServletRequest#getPathTranslated
/**
* Returns any extra path information after the servlet name but before the
* query string, and translates it to a real path. Same as the value of the
* CGI variable PATH_TRANSLATED.
* <p>
* If the URL does not have any extra path information, this method returns
* <code>null</code> or the servlet container cannot translate the virtual
* path to a real path for any reason (such as when the web application is
* executed from an archive). The web container does not decode this string.
*
* @return a <code>String</code> specifying the real path, or
* <code>null</code> if the URL does not have any extra path
* information
*/
public String getPathTranslated();
getRealPath方法采用String参数,并返回路径对应的本地文件系统上文件的String表示形式。 getPathTranslated方法计算请求的pathInfo的真实路径。
在servlet容器无法为这些方法确定有效文件路径的情况下,例如从归档文件执行Web应用程序时,在本地无法访问的远程文件系统上或在数据库中,这些方法必须返回null。 仅当调用getRealPath时容器已将其从其包含的JAR文件中解压缩时,才必须考虑JAR文件的META-INF/resources目录,并且在这种情况下,必须返回解压缩的位置。
realPath = D:\app\webapp\index.html
pathTranslated = D:\app\webapp\1
七、非阻塞IO
Web容器中的非阻塞请求处理有助于提高对Web容器可伸缩性不断增长的需求,并增加Web容器可以同时处理的连接数。 Servlet容器中的非阻塞IO允许开发人员在数据可用时读取数据,或在可能的情况下写入数据。 非阻塞IO仅与Servlet和过滤器中的异步请求处理以及升级处理一起使用。 否则,在调用ServletInputStream.setReadListener或ServletOutputStream.setWriteListener时必须抛出IllegalStateException。
对应的接口为javax.servlet.ReadListener
.
package javax.servlet;
import java.io.IOException;
/**
* Receives notification of read events when using non-blocking IO.
*
* @since Servlet 3.1
*/
public interface ReadListener extends java.util.EventListener{
/**
* Invoked when data is available to read. The container will invoke this
* method the first time for a request as soon as there is data to read.
* Subsequent invocations will only occur if a call to
* {@link ServletInputStream#isReady()} has returned false and data has
* subsequently become available to read.
*
* @throws IOException id an I/O error occurs while processing the event
*/
public abstract void onDataAvailable() throws IOException;
/**
* Invoked when the request body has been fully read.
*
* @throws IOException id an I/O error occurs while processing the event
*/
public abstract void onAllDataRead() throws IOException;
/**
* Invoked if an error occurs while reading the request body.
*
* @param throwable The exception that occurred
*/
public abstract void onError(java.lang.Throwable throwable);
}
比如tomcat中案例:
首先在web.xml必须进行设置
<!-- Non-blocking IO examples -->
<servlet>
<servlet-name>bytecounter</servlet-name>
<servlet-class>nonblocking.ByteCounter</servlet-class>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>bytecounter</servlet-name>
<url-pattern>/servlets/nonblocking/bytecounter</url-pattern>
</servlet-mapping>
对应Servlet源码如下(可以直接在Tomcat案例当中找到)
package nonblocking;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This doesn't do anything particularly useful - it just counts the total
* number of bytes in a request body while demonstrating how to perform
* non-blocking reads.
*/
public class ByteCounter extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println("Try again using a POST request.");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
// Non-blocking IO requires async
AsyncContext ac = req.startAsync();
// Use a single listener for read and write. Listeners often need to
// share state to coordinate reads and writes and this is much easier as
// a single object.
@SuppressWarnings("unused")
CounterListener listener = new CounterListener(
ac, req.getInputStream(), resp.getOutputStream());
}
/**
* Keep in mind that each call may well be on a different thread to the
* previous call. Ensure that changes in values will be visible across
* threads. There should only ever be one container thread at a time calling
* the listener.
*/
private static class CounterListener implements ReadListener, WriteListener {
private final AsyncContext ac;
private final ServletInputStream sis;
private final ServletOutputStream sos;
private volatile boolean readFinished = false;
private volatile long totalBytesRead = 0;
private byte[] buffer = new byte[8192];
private CounterListener(AsyncContext ac, ServletInputStream sis,
ServletOutputStream sos) {
this.ac = ac;
this.sis = sis;
this.sos = sos;
// In Tomcat, the order the listeners are set controls the order
// that the first calls are made. In this case, the read listener
// will be called before the write listener.
sis.setReadListener(this);
sos.setWriteListener(this);
}
@Override
public void onDataAvailable() throws IOException {
int read = 0;
// Loop as long as there is data to read. If isReady() returns false
// the socket will be added to the poller and onDataAvailable() will
// be called again as soon as there is more data to read.
while (sis.isReady() && read > -1) {
read = sis.read(buffer);
if (read > 0) {
totalBytesRead += read;
}
}
}
@Override
public void onAllDataRead() throws IOException {
readFinished = true;
// If sos is not ready to write data, the call to isReady() will
// register the socket with the poller which will trigger a call to
// onWritePossible() when the socket is ready to have data written
// to it.
if (sos.isReady()) {
onWritePossible();
}
}
@Override
public void onWritePossible() throws IOException {
if (readFinished) {
// Must be ready to write data if onWritePossible was called
String msg = "Total bytes written = [" + totalBytesRead + "]";
sos.write(msg.getBytes(StandardCharsets.UTF_8));
ac.complete();
}
}
@Override
public void onError(Throwable throwable) {
// Should probably log the throwable
ac.complete();
}
}
}
返回结果如下:
Total bytes written = [166773]
八、Cookies
HttpServletRequest接口提供getCookies方法来获取请求中存在的cookie数组。 这些cookie是应客户端发出的每个请求从客户端发送到服务器的数据。 通常,客户端作为cookie的一部分发送回的唯一信息是cookie名称和cookie值。 通常不会返回将cookie发送到浏览器时可以设置的其他cookie属性,例如注释。 该规范还允许cookie为HttpOnly cookie。 HttpOnly cookie向客户端指示不应将其暴露给客户端脚本代码(除非客户端知道要寻找该属性,否则不会将其过滤掉)。 HttpOnly cookie的使用有助于减轻某些类型的跨站点脚本攻击。
对应的代码
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class CookieExample extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// print out cookies
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {
Cookie c = cookies[i];
String name = c.getName();
String value = c.getValue();
out.println(name + " = " + value);
}
// set a cookie
String name = request.getParameter("cookieName");
if (name != null && name.length() > 0) {
String value = request.getParameter("cookieValue");
Cookie c = new Cookie(name, value);
response.addCookie(c);
}
}
}
从以上案例中不难看出cookies保存在浏览器而不是服务器当中,客户端发起请求会在request参数当中带上所有的Cookie信息,在服务器端通过javax.servlet.http.HttpServletRequest#getCookies
获取,也可以通过在服务器端通过javax.servlet.http.HttpServletResponse#addCookie
方法将指定的Cookie添加到返回参数,然后发送到浏览器,浏览器在缓存到本地。这样只要是之前请求过的浏览器就会被标识。
九、SSL参数
如果请求已通过安全协议(例如HTTPS)传输,则此信息必须通过ServletRequest接口的isSecure方法公开。 Web容器必须向Servlet程序员公开以下属性(参考类org.apache.tomcat.util.net.SSLSupport
):
/**
* The Request attribute key for the cipher suite.
*/
public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";
/**
* The Request attribute key for the key size.
*/
public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
/**
* The Request attribute key for the session id.
* This one is a Tomcat extension to the Servlet spec.
*/
public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session_id";
如果存在与该请求关联的SSL证书,则该Servlet容器必须将其作为java.security.cert.X509Certificate类型的对象数组公开给servlet程序员,并可以通过javax.servlet.request的ServletRequest属性进行访问X509证书。 该数组的顺序定义为信任的升序。 链中的第一个证书是客户端设置的证书,下一个是用于认证第一个证书的证书,依此类推。
十、国际化
客户端可以选择向Web服务器指示他们希望以哪种语言给出响应。可以使用Accept-Language标头以及HTTP / 1.1规范中描述的其他机制从客户端传达此信息。 ServletRequest接口中提供了以下方法来确定发送者的首选语言环境:
/**
* Returns the preferred <code>Locale</code> that the client will accept
* content in, based on the Accept-Language header. If the client request
* doesn't provide an Accept-Language header, this method returns the
* default locale for the server.
*
* @return the preferred <code>Locale</code> for the client
*/
public Locale getLocale();
/**
* Returns an <code>Enumeration</code> of <code>Locale</code> objects
* indicating, in decreasing order starting with the preferred locale, the
* locales that are acceptable to the client based on the Accept-Language
* header. If the client request doesn't provide an Accept-Language header,
* this method returns an <code>Enumeration</code> containing one
* <code>Locale</code>, the default locale for the server.
*
* @return an <code>Enumeration</code> of preferred <code>Locale</code>
* objects for the client
*/
public Enumeration<Locale> getLocales();
getLocale方法将返回客户端要接受其内容的首选语言环境。 getLocales方法将返回一个Locale对象的Enumeration,以从首选语言环境开始的降序指示客户端可接受的语言环境。如果客户端未指定首选语言环境,则getLocale方法返回的语言环境必须是 Servlet容器的默认语言环境,并且getLocales方法必须包含默认语言环境的单个Locale元素的枚举。
编写一个根据语言环境返回不同值的Servlet
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Locale;
@MultipartConfig
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
Locale locale = request.getLocale();
System.out.println("locale = " + locale.getDisplayName());
System.out.println("---------------------------------------");
Enumeration<Locale> localeEnumeration = request.getLocales();
while (localeEnumeration.hasMoreElements()) {
Locale element = localeEnumeration.nextElement();
System.out.println(element + "=" + element.getDisplayName());
}
String value = "";
if (locale.equals(Locale.CHINA)) {
value = "您好";
} else if (locale.equals(Locale.UK)) {
value = "Hello";
}
System.out.println("-------------------I am DemoServlet----------------");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>" + value + "</h1>");
out.flush();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("-------------------DemoServlet init----------------");
}
}
当前浏览器的语言环境配置如下
此时通过浏览器发起请求http://localhost:8080/webapp/demo/1
将返回“您好”。
控制台打印如下
-------------------DemoServlet init----------------
locale = 中文 (中国)
zh_CN=中文 (中国)
zh=中文
en_GB=英文 (英国)
en=英文
-------------------I am DemoServlet----------------
修改语言优先级
此时浏览器返回值为“Hello”
十一、请求数据编码
当前,许多浏览器不发送带有ContentType头的char编码限定符,而保留了用于读取HTTP请求的字符编码的确定。 如果客户端请求未指定容器用于创建请求读取器和解析POST数据的请求的默认编码,则必须为“ ISO-8859-1”。 但是,在这种情况下,为了向开发人员指示客户端发送字符编码失败,容器从getCharacterEncoding方法返回null。
如果客户端未设置字符编码,并且请求数据使用与上述默认编码不同的编码进行编码,则可能会发生乱码。 为了解决这种情况,已将新方法setCharacterEncoding(String enc)添加到ServletRequest接口。 开发人员可以通过调用此方法来覆盖容器提供的字符编码。 在解析任何发布数据或从请求中读取任何输入之前,必须先调用它。 读取数据后调用此方法将不会影响编码。
编写网页
<!DOCTYPE HTML><html lang="en">
<head>
<meta charset="UTF-8">
<title>Apache Tomcat Examples</title>
</head>
<body>
<form action="http://localhost:8080/webapp/demo/1" method="post" >
First name:<br>
<input type="text" name="firstname">
<br>
Last name:<br>
<input type="text" name="lastname">
<br>
<input type="submit" value="Submit">
</form>
<p>如果您点击提交,表单数据会被发送到对应Servlet请求。</p>
</body>
</html>
修改Servlet
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@MultipartConfig
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String characterEncoding = request.getCharacterEncoding();
System.out.println("request characterEncoding = " + characterEncoding);
System.out.println("firstname = " + request.getParameter("firstname"));
System.out.println("lastname = " + request.getParameter("lastname"));
response.setContentType("text/html");
System.out.println("response characterEncoding = " + response.getCharacterEncoding());
PrintWriter out = response.getWriter();
out.println("<h1>您好</h1>");
out.flush();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("-------------------DemoServlet init----------------");
}
}
参数都设置为中文并提交
控制台为乱码,而且获取到编码为空。
返回值也为乱码
解决上一个问题需要设置请求编码,而解决下一个问题需要设置响应编码,因为请求的页面的编码为UTF-8,所以都需要设置为UTF-8。如下所示
结果如下,问题得到解决
一般不会在Servlet中如此设置编码,而是在通用过滤器中设置好
十二、请求对象的生命周期
每个请求对象仅在servlet的服务方法范围内或在过滤器的doFilter方法范围内有效,除非对该组件启用了异步处理并且在请求对象上调用了startAsync方法。 在发生异步处理的情况下,请求对象将保持有效,直到在AsyncContext上调用complete为止。 容器通常回收请求对象,以避免产生请求对象的性能开销。 开发人员必须意识到,不建议维护对未在上述范围之外调用startAsync的请求对象的引用,因为这样做可能会产生不确定的结果。
总结
以上我们对Servlet请求参数Request接口进行了详细的介绍,目前Servlet的主要实现协议是HTTP,因为对应的类实际为javax.servlet.http.HttpServletRequest
。通过这个对象不但可以获取从客户端发过来的参数,而且还可以设置属性用于在请求对象的生命周期内共享变量(同一个Servlet请求内)。HTTP请求本质上是没有状态的,所以产生了Cookie和Session的概念,本文并没有介绍Session,可以去查看其它文档。