图源:
在中介绍了如何编写和运行一个基于Servlet编写的Web应用,本篇文章将学习更多Servlet应用中如何使用常见的Web技术。
重定向和转发
页面重定向应该不会陌生,我们只需要在返回的HTTP响应报文中,将响应状态码设置为30X,并且通过报文头标签Location
指定重定向后的目标URL即可。
类似之前的实现,先设置一个欢迎页面:
package cn.icexmoon.java.note.ch28;
// ...
urlPatterns = "/")
(public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
if (name == null) {
name = "none";
}
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<h1>This is a first servlet app example.</h1>");
String msg = String.format("<div>hello %s</div>", name);
writer.print(msg);
writer.flush();
}
}
设置一个页面,访问该页面的请求将重定向到欢迎页面:
package cn.icexmoon.java.note.ch28;
// ...
urlPatterns = "/redirect")
(public class RedirectServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
//302 临时重定向
// resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
//301 永久重定向
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
String url = "/hello/";
if (name != null) {
url += String.format("?name=%s", name);
}
resp.setHeader("Location", url);
}
}
现在如果请求/hello/redirect
,浏览器将会重定向到/hello/
这个页面。
通常使用的重定向状态码有301和302,分别对应常量
SC_MOVED_PERMANENTLY
和SC_MOVED_TEMPORARILY
,它们的区别是301是永久重定向,浏览器通常会缓存这个重定向目标页面,下次请求会利用本地缓存直接访问目标页面,302是临时重定向,浏览器不会缓存这种重定向关系。
需要注意的是,重定向实际上是服务端让客户端重新请求另一个URL,所以如果这种方式实现的页面跳转,还想保留某些请求信息,比如请求原始页面时附加的查询参数(这个例子里就是?name=xxx
),就需要在重定向时服务端附加上相应的查询参数,比如示例中的url += String.format("?name=%s", name)
。
除了利用HTTP的重定向实现HTTP请求“跳转”以外,还可以直接在Servlet应用内部将一个请求转交给另一个Servlet类来处理:
package cn.icexmoon.java.note.ch28;
// ...
urlPatterns = "/transmit")
(public class TransmitServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/").forward(req, resp);
}
}
这种方式实现的结果是客户端不会再次请求其它URL,而是在第一次请求后就获取到转发后的目标结果(页面)。
cookie和session
cookie和session是进行Web编程时常用的状态保持技术,下面看如何在Servlet应用中使用。
假设这里我们需要模拟用户登录和注销的操作,简单起见,这里用一个Map
用于登录相关的Servlet:
package cn.icexmoon.java.note.ch28;
// ...
urlPatterns = "/sign")
(public class SignServlet extends HttpServlet {
private static Map<String, String> users = Collections.synchronizedMap(new HashMap<>());
static {
users.put("icexmoon", "123");
}
/**
* 返回登录页面
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = (String) req.getSession().getAttribute("user");
if (user != null){
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
resp.setHeader("Location", "/hello/home");
return;
}
resp.setContentType("text/html; charset=UTF-8");
PrintWriter writer = resp.getWriter();
String pageContent = this.getSignPage();
writer.print(pageContent);
writer.flush();
}
/**
* 执行登录动作
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String password = req.getParameter("password");
if (!checkLogin(name, password)){
resp.setContentType("text/html; charset=UTF-8");
//登录失败
PrintWriter writer = resp.getWriter();
writer.print("<h1>用户名或密码错误</h1>");
writer.flush();
}
else {
//登录成功
//写入session
req.getSession().setAttribute("user", name);
//跳转到home页
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
resp.setHeader("Location", "/hello/home");
}
}
/**
* 检查用户密码是否正确
* @param name
* @param password
* @return
*/
private static boolean checkLogin(String name, String password){
if (name == null || name.isEmpty()){
return false;
}
if (password == null || password.isEmpty()){
return false;
}
if (!users.containsKey(name)){
return false;
}
String correctPass = users.get(name);
if (correctPass == null){
return false;
}
if (!correctPass.equals(password)){
return false;
}
return true;
}
private static String getSignPage() {
StringBuilder sb = new StringBuilder();
sb.append("<form action=\"/hello/sign\" method=\"post\">");
sb.append("用户名<input name=\"name\"/><br/>");
sb.append("密码<input name=\"password\"/><br/>");
sb.append("<button type=\"submit\">登录</button>");
sb.append("</form>");
return sb.toString();
}
}
这里需要注意的是,保存用户信息的Map
是一个线程安全的同步容器,因为Web应用一般都是多线程的,服务端同时可以为多个客户端提供服务,这里实际上是由Web Server(比如Tomcat)来进行多线程调度的,并且很可能会进行线程复用。
在客户端执行POST请求/hello/sign
后,服务端会检查用户名和密码,如果正确,就可以认为用户已处于登录状态,这个状态我们通过向Session中写入用户名来实现。在Servlet中,可以用req.getSession().setAttribute("user", name)
这样的方式向Session中写入信息。
在客户端执行GET请求/hello/sign
后,服务端会从通过从Session中获取用户名的方式来判断当前用户是否已经登录,如果登录用户名是什么。具体通过req.getSession().getAttribute("user")
来获取Session中的指定信息。
下面是一个用户主页:
package cn.icexmoon.java.note.ch28;
// ...
urlPatterns = "/home")
(public class HomeServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = (String) req.getSession().getAttribute("user");
if (name == null) {
//没有登录
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
resp.setHeader("Location", "/hello/sign");
return;
}
//如果客户端没有记录天气,就写入cookie
String clientWeather = getWeatherFromCookie(req);
String nowWeather;
if (clientWeather == null) {
String weather = getWeather();
writeWeather2Cookie(resp, weather);
nowWeather = weather;
} else {
nowWeather = clientWeather;
}
//已经登录
resp.setContentType("text/html; charset=UTF-8");
PrintWriter writer = resp.getWriter();
//显示天气
writer.print(String.format("<div>%s</div>", nowWeather));
writer.print("<h1>This is your home page</h1>");
writer.print(String.format("<div>hello, %s</div>", name));
writer.print("<div><a href=\"/hello/exit\">注销</a></div>");
writer.flush();
}
private static String getWeather() {
String[] weathers = {"天气晴 15度~20度", "天气阴 10度~20度", "多云转阴 15度~22度"};
Random random = new Random();
int i = random.nextInt(weathers.length);
return weathers[i];
}
private String getWeatherFromCookie(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies == null) {
return null;
}
for (Cookie c : cookies) {
if (c.getName().equals("weather")) {
String weather = c.getValue();
weather = new String(Base64.getDecoder().decode(weather), Charset.forName("UTF-8"));
return weather;
}
}
return null;
}
private void writeWeather2Cookie(HttpServletResponse resp, String weather) {
String encodedWeather = Base64.getEncoder().encodeToString(weather.getBytes(StandardCharsets.UTF_8));
Cookie cookie = new Cookie("weather", encodedWeather);
//有效期为1天
cookie.setMaxAge(24 * 60 * 60);
resp.addCookie(cookie);
}
}
在主页上,我添加了一个显示当前天气情况的功能(示例中天气获取用一个随机产生的方式替代)。因为天气情况实际上并不重要,我们甚至可以允许用户进行篡改,所以这里利用Cookie来保存用户当地的天气情况。
当服务端获取到用户当地天气后,就可以通过设置响应报文头Set-Cookie
的方式将该信息保存到客户端cookie中。当然Servlet已经封装好了,这里只需要创建一个Cookie
对象,并通过resp.addCookie
方法添加即可。
这里需要注意两点:
-
给客户端添加Cookie时通常需要指定有效期(时长,单位秒),这里指定的是1天。
-
保存Cookie时只能是ASCII字符,如果是中文之类的,就会报错,这里需要通过Base64编码进行转码后保存,相应的,读取时也需要进行Base64解码。
其它部分代码相对简单,这里不详细介绍,具体可以从 获取完整示例代码。
好了,就到这里了,谢谢阅读。
文章评论