3.2 关于HttpServletRequest
当HTTP请求交给Web容器处理时,Web容器会收集相关信息,并产产生HttpServletRequest对象,可以使用这个对象取得HTTP请求中的信息。可以在Servlet中进行请求的处理,或是转发(包含)另一个Servlet/JSP进行处理。各个Servlet/JSP在同一请求周期中要有共用的资料,可以设置在请求对象中成为属性。
3.2.1处理请求参数
请求来到服务器时,Web容器会创建HttpServleRequest实例来包装请求中的相关信息,HttpServletRequest定义了取得请求信息的方法。例如,可以使用以下方法来取得请求参数。
getParameter():指定请求参数名称来取得对应的值。例如:
String username = request.GetParameter("name");
在2.1.2节中就看过取得请求参数的范例,就API而言,请求参数的取得本身不是难事,然而在考虑Web应用程序安全性时,出发点就是:"永远别假设使用者会按照你的期望提供请求信息。"
例如,2.1.2节中的第一个Servlet范例中,苦使用浏览器请求:
http://localhost:8080/FirstServlet/hello?name=%3Csmall%3EJustin%3C/smal%3E
那么响应画面中,可以看到显示的字变小了,如图3.6所示:
图3.6 未经过滤的请求
这是因为name=%3Csmall%3EJustin%3C/smal%3E其实是name=<small>Justin</smal>经过URI编码后的结果,也就是说Servlet取得了name请求参数值,等同于"<small>Justin</small>",这个值未任何处理就输出至浏览器。结果是:
<!DOCTYPE html> <html> <head> <title>Hello</title> </head> <body> <h1> Hello! <small>Justin</smal>!</h1> </body></html> |
也就是说<small>Justin<small>成为了HTML的一部分了,单就这个简单范例来说,可以注入任何信息 ,甚至是JavaScript。例如,指定请求参数name=%3Cscript%3Ealert(%22Atack%22)%3C/script%3E,也就是name=<script>alert("Atack")</script>的URI编码,就会发现注入的JavaScript程序代码也输出至浏览器执行了,如图3.7所示 。
图3.7 浏览器执行了注入的JavaScript
简单来说,未经过滤的请求参数值会形成Web网站的弱点,引发各种可能注入(Injection)攻击可能,刚才的例子就有可能进一步发展成某种XSS(Cross Site Script)攻击。如果请求参数值未经过滤,且进一步成为后端SQL查询语句的一部分,就有可能发生SQL注入(SQL Injection)安全问题,若请求参数未经过滤就成为网址重导的一部分,就有可以成为攻击的跳板,这也是为什么在图1.12中2013年、2017年OWASP TOP里,第一名都是Injection。
要防止注入的弱点发生,必须对请求进行验证,只不过注入的模式多而复杂,该做什么验证,实际上得视应用程序的设计而定,这部分应该从专门讨论完全的书籍中学习。
单就这简单的范例来说,基本的防范方式可以将使用者输入的"<"、“>”过滤,并将其转换为对应的HTML实例名称(Entity name)<、%gt;,例如:
Request Hello.java
package com.tjjingpan.Request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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.Optional;
@WebServlet("/hello")
public class Hello extends HttpServlet {
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String name = Optional.ofNullable(request.getParameter("name"))
.map(value -> value.replaceAll("<", "<"))
.map(value -> value.replaceAll(">", ">"))
.orElse("Guest");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("<title>Hello</title>");
out.print("</head>");
out.print("<body>");
out.printf("<h1> Hello! %s!</h1>", name);
out.print("</body>");
out.print("</html>");
}
}
由于没有指定的请求参数名称时,getParameter()会传回null,基本上可以使用if来检查是否为null。Servlet4.0是基于Java EE 8的,而Java EE 8是基本Java SE 8,在这里使用Java SE 8的Optiona API来提高代码撰写的流畅度与可读性。
如果请求参数确实存丰,map()方法会传入参数值,此时将"<"">"转换为对应的%lt;、>,最后的orElse()在请求参数存在时,会返回转换后的字符串,若请求参数不存在,会返回指定的字符串值,在范例中指定为"Guest"。
HTML实例名称只会在浏览器上显示对应的字符,因此同样的注入模式,现在只会看到如图3.8所示的结果。
图3.8 基本的注入防范
不过,这类为了安全而撰写的程序代码,若夹杂在一般的商务流程之中,会使商务流程程序代码不易阅读,为了安全而撰写的程序代码比较适合设计为拦截器(Interceptor)之类的组件,适时地设定给应用程序,例如在第5章介绍到的过滤器(Filter),会比较适合用来设计这类安全组件。
3.2.2 处理请求标头
Request Header.java
package com.tjjingpan.Request;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/header")
public class Header extends HttpServlet {
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("<title>Show Headers</title>");
out.print("</head>");
out.print("<body>");
Collections.list(request.getHeaderNames())
.forEach(name -> {
out.printf("%s: %s<br>",
name, request.getHeader(name));
});
out.print("</body>");
out.print("</html>");
}
}
这个范例坠子介绍getHeaderNames()与getHeader()方法的使用外,还示范了如何将Enumeration转为ArrayList,以便进一步使用forEach()方法,结果如图3.9所示.
图3.9 查看浏览器所送出的标头
3.2.3 请求参数编码处理
3.2.4 getReader()、getInputStream()读取内容
HttpServletRequest定义有getReader()方法,可以取得一个BufferedReader,通过该对象可以读取请求的Body数据。例如,使用下面这个范例来读取请求Body内容:
Request BodyBody.java
package cc.openhome;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/postbody")
public class BodyBody extends HttpServlet {
protected void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("<title>Hello</title>");
out.print("</head>");
out.print("<body>");
out.println(bodyContent(request.getReader())); //取得BufferedReader
out.print("</body>");
out.print("</html>");
}
private String bodyContent(BufferedReader reader) throws IOException {
String input = null;
StringBuilder requestBody = new StringBuilder();
while((input = reader.readLine()) != null) {
requestBody.append(input).append("<br>");
}
return requestBody.toString();
}
}
可试着对这个Servlet以下窗体发现请求:
Request postbody.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="postbody" method="post">
UserName:<input type="text" name="User"><br>
PassWord:<input type="password" name="passwd"><br>
<input type="submit" name="发送查询">
</form>
</body>
</html>
如果在"名称"字段输入"良葛格",在"密码"字段输入123456,单击"发送"按钮后,则会看到图3.10所示的内容:
图3.10 查看浏览器送出的请求Body
回忆第1章介绍的URI编码,可以看到"良葛格"三字的编码是%E8%89%AF%E8%91%9B%E6%A0%BC,而发送查询的编码是%E5%8F%91%E9%80%81%E6%9F%A5%E8%AF%A2,提交的URI编码是%E6%8F%90%E4%BA%A4
User=%E8%89%AF%E8%91%9B%E6%A0%BC&passwd=123456&%E5%8F%91%E9%80%81%E6%9F%A5%E8%AF%A2=%E6%8F%90%E4%BA%A4
User=良葛格&passwd=123456&发送查询=提交
窗体发送时,如果<form>标签没有设置enctype属性,则默认值就是application/x-www-form-urlencode.如果要上传文件,enctype属性要设为multipart/form-data。如果使用 下窗体选择一个文件发送:
Request upload.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="postbody" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="filename" value=""/><br>
<input type="submit" value="Upload" name="upload"/>
</form>
</body>
</html>
例如,使用Chrome浏览器发送一个JPG图片,则在网页上会看到:
------WebKitFormBoundarycYavuQ0Bs5PdVNaR
Content-Disposition: form-data; name="filename"; filename="upload.jpeg"
Content-Type: image/jpeg
ÿØÿàJFIFHHÿÛC
! #'2*#%/%+;,/35888!*=A<6A2785ÿÛC
5$$55555555555555555555555555555555555555555555555555ÿÀ‹Š"ÿÄÿÄS
!1AQaq¡±"2‘Á#BRrÑ3báCS‚¢²ð$4’“%56DTcsƒÂEUdÒâñ&t£ÿÄÿÄ81!AQ"2aðBq¡±3‘ÁÑá#Rñ4C$b‚ÿÚ?í(ˆ€""ˆˆ" Š ·i;‚Ò´7ksÎYÝž®ÇkK¹{(°½íª˜£,“w;RUh „¢"ˆˆ" ˆ€""ˆˆ" ˆ€"(&úÆÈq#uõTó±×u‡Vöjl9
.
.
.省略
À"ˆˆ" ˆ€""ˆˆ" ˆ€""ˆˆò”‚€n¦ü{§Kõ{AÜPÆþ·]Û?uTÖlHHøN¶íTÈ-FÜ›í¥±7¶îåLcfw ßeê€""ˆˆ" ˆ€""ˆˆÿÙ
------WebKitFormBoundarycYavuQ0Bs5PdVNaR
Content-Disposition: form-data; name="upload"
Upload
------WebKitFormBoundarycYavuQ0Bs5PdVNaR--
总之是一堆奇奇怪怪的字符,这些字符是实际的文件内容。
提示>>> 接下来的范例内容是进阶内容,可以跳过不看,不影响之后的内容了解。如果想要了解接下来这些进阶内容,可以到JWork@TW(www.javaworld.com.tw)论坛全文搜索"HTTP文件上传机制解析"。
例如,可能使用以下Servlet来处理一个上传的文件:
Request Upload.java
package com.tjjingpan.Request;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/upload")
public class Upload extends HttpServlet {
private final Pattern fileNameRegex = Pattern.compile("filename=\"(.*)\"");
private final Pattern fileRangeRegex = Pattern.compile("filename=\".*\"\\r\\n.*\\r\\n\\r\\n(.*+)");
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
byte[] content = bodyContent(request);
String contentAsTxt = new String(content, "ISO-8859-1");
String filename = filename(contentAsTxt);
Range fileRange = fileRange(contentAsTxt, request.getContentType());
write(
content,
contentAsTxt.substring(0, fileRange.start)
.getBytes("ISO-8859-1")
.length,
contentAsTxt.substring(0, fileRange.end)
.getBytes("ISO-8859-1")
.length,
String.format("d:/javaprojects/jspservlet/chapt03/Request/%s", filename)
);
}
private byte[] bodyContent(HttpServletRequest request) throws IOException {
try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
InputStream in = request.getInputStream();
byte[] buffer = new byte[1024];
int length = -1;
while((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
return out.toByteArray();
}
}
private String filename(String contentTxt) throws UnsupportedEncodingException {
Matcher matcher = fileNameRegex.matcher(contentTxt);
matcher.find();
String filename = matcher.group(1);
if(filename.contains("\\")) {
return filename.substring(filename.lastIndexOf("\\") + 1);
}
return filename;
}
private static class Range {
final int start;
final int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
}
private Range fileRange(String content, String contentType) {
Matcher matcher = fileRangeRegex.matcher(content);
matcher.find();
int start = matcher.start(1);
String boundary = contentType.substring(
contentType.lastIndexOf("=") + 1, contentType.length());
int end = content.indexOf(boundary, start) - 4;
return new Range(start, end);
}
private void write(byte[] content, int start, int end, String file) throws IOException {
try(FileOutputStream fileOutputStream = new FileOutputStream(file)) {
fileOutputStream.write(content, start, (end - start));
}
}
}
这里的代码比较冗长,主要概念是使用规则表示(Regular expression)来判断文件名与文件内容边界,程序将流程切割为数个子方法,每个方法的作用均以批注说明。可以将前面upload.html中的<form>的action属性改为upload,就可以上传文件了。
3.2.5 getPart()、getParts()取得上传文件
从上一节使用getInputStream()处理文件上传相关事宜可以看到,处理过程比琐碎,在Servlet3.0中,新增了Part接口,可以方便地进行文件上传处理。可以通过HttpServletRequest的getPart()方法取得Part对象。例如,有个上传窗体内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="photo" method="post"
enctype="multipart/form-data">
上传相片:<input type="file" name="photo" /><br><br>
<input type="submit" value="上传" name="upload" />
</form>
</body>
</html>
可以编写一个Servlet来进行文件上传的处理,这次使用getPart()来处理上传的文件:
Request Photo.java
package com.tjjingpan.Request;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@MultipartConfig//必须设置此标注才能使用getPart()相关API
@WebServlet("/photo")
public class Photo extends HttpServlet {
private final Pattern fileNameRegex =
Pattern.compile("filename=\"(.*)\"");
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part photo = request.getPart("photo");//使用getPart()取得Part对象
String filename = getSubmittedFileName(photo);
write(photo, filename);
}
private String getSubmittedFileName(Part part) {//取得上传文件名
String header = part.getHeader("Content-Disposition");
Matcher matcher = fileNameRegex.matcher(header);
matcher.find();
String filename = matcher.group(1);
if(filename.contains("\\")) {
return filename.substring(filename.lastIndexOf("\\") + 1);
}
return filename;
}
//存储文件
private void write(Part photo, String filename) throws IOException, FileNotFoundException {
try(InputStream in = photo.getInputStream();
OutputStream out = new FileOutputStream(
String.format("d:/javaprojects/jspservlet/chapt03/Request/%s", filename))) {
byte[] buffer = new byte[1024];
int length = -1;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
}
}
}
@MultipartConfig标注可用来设置Servlet处理上传文件的相关信息,在上例中仅标注@MultipartConfig而没有设置任何属性,这表示相关属性采用默认值。@MultipartConfig的可用属性如下。
- fileSizeThreshoud
- location
- maxFileSize
- maxRequestSize
Part有个方便的write()方法,可以直接将上传文件指定文件名写入磁盘中,write()可指定文件名,写入的路径是相对于@MultipartConfig的location设置的路径。例如,上例可以修改为:
Request Photo2
package com.tjjingpan.Request;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@MultipartConfig(location="d:/javaprojects") //设定location属性
@WebServlet("/photo2")
public class Photo2 extends HttpServlet {
private final Pattern fileNameRegex =
Pattern.compile("filename=\"(.*)\"");
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8"); //为了处理中文文件名
Part photo = request.getPart("photo");
String filename = getSubmittedFileName(photo);
photo.write(filename); //将文件写入location指定目录
}
private String getSubmittedFileName(Part part) {
String header = part.getHeader("Content-Disposition");
Matcher matcher = fileNameRegex.matcher(header);
matcher.find();
String filename = matcher.group(1);
if(filename.contains("\\")) {
return filename.substring(filename.lastIndexOf("\\") + 1);
}
return filename;
}
}
在这个范例中,设置了@MultiPartConfig的location属性。由于上传的文件名可能会有中文,所以调用setCharacterEncoding()设置正确的编码。最后使用Part的write()直接将文件写入location属性指定的目录,这可以简化文件IO的处理。
如果有多个文件要上传 ,可以使用getParts()方法,会返回一个Collection<Part>,其中有每个上传文件的Part对象。例如,有如下窗体:
Request uploads.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="uploads" method="post"
enctype="multipart/form-data">
文件 1:<input type="file" name="file1" /><br>
文件 2:<input type="file" name="file2" /><br>
文件 3:<input type="file" name="file3" /><br><br>
<input type="submit" value="上传" name="upload" />
</form>
</body>
</html>
则可台使用以下Servlet来处理文件上传请求:
Request Uploads.java
package com.tjjingpan.Request;
import java.io.*;
import java.time.Instant;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@MultipartConfig(location="d:/javaprojects")
@WebServlet("/uploads")
public class Uploads extends HttpServlet {
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
request.getParts()
.stream()//使用Stream
.filter(part -> part.getName().startsWith("file"))//只处理上传文件区段
.forEach(this::write);
}
private void write(Part part) {
String submittedFileName = part.getSubmittedFileName();
//取得扩展名
String ext = submittedFileName.substring(submittedFileName.lastIndexOf('.'));
try {
//使用时间毫秒数为主文件名
part.write(String.format("%s%s", Instant.now().toEpochMilli(), ext));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
在这个范例中,使用Java 8 Stream API,由于"上传"按钮也会是其中一个Part对象,先判断Part的名称是不是以file开头,可以使用Part的getName()来取得名称。进一步过滤出文件上传区段的Part对象,然后取得扩展名。为了避免上传后可能发生的文件名重复问题,以获取系统时间之毫秒数作为主文件名写入文件。
如果要使用web.xml设置@multipartConfig对应的信息,则可以如下:
... <servlet> <servlet-name>uploads</servlet-name> <servlet-class>com.tjjingpan.Request.Uploads</servlet-class> <multipart-config> <location>d:/javaprojects</location> </multipart-config> </servlet> <servlet-mapping> <servlet-name>uploads</servlet-name> <url-pattern>/uploadsfiles</url-pattern> </servlet-mapping> ...
3.2.6 使用RequestDispatcher调派请求
1.使用include()方法
RequestDispatcher的include()方法,可以将另一个Servlet的操作流程包括到目前Servlet操作流程之中。例如:
Request Some.java
package com.tjjingpan.Request;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/some")
public class Some extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("Some do one...");
RequestDispatcher dispatcher = request.getRequestDispatcher("other");
dispatcher.include(request, response);
out.println("Some do two...");
}
}
other.view实际上会依URI模式取得对应的Servlet。调用include()时,必须分别传入实现ServletRequest、RequestResponse接口对象,可以是service()方法传入的对象或者是自定义的对象或封装器(之后章节介绍封装器的编写)。如果被include()的Servlet是这么编写的:
Request Other.java
package com.tjjingpan.Request;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/other")
public class Other extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Other do one...");
}
}
则网页上见到的响应顺序是Some do one... Other do one ... Some do two...。
在取得RequestDispatcher时,也可以包括查询字符串。例如:
RequestDispatcher dispatcher = request.getRequestDispatcher("other?data=123456")
那么在被包含(或转发,如果使用的是forward())的Servlet中就可以使用getParameter("data")取得请求参数值。
2.请求范围属性
在include()或forward()时包括请求参数的做法,仅适用于传递字符串值给另一个Servlet,在调派请求的过程中,如果有必须共享的"对象",可以设置给请求对象成为属性,称为请求范围属性(Request Scope Attribute)。
3.使用forward()方法
RequestDispatcher有个forward()方法,调用时同样传入请求与响应对象,这表示要将请求处理转发给别的Servlet,“对浏览器的响应同时也转发给另一个Servlet”。
由于请求的include()或forward(),是属性容器内部流程的调派,而不是在响应中要求浏览器重新请求某些URI,因此浏览器不会知道实际上的流程调派,也就是说浏览器的地址栏不会有任何变化。
第1章曾经介绍过Model 2,在了解请求调派的处理方式之后,这里先做一个简单的Model 2架构应用程序,一方面应用刚才学习到的请求调派处理,另一方面初步了解Model 2的基本流程。首先看控制器(Controller)。它通常由一个Servlet为实现:
Model2 HelloController.java
package com.example.Model2;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloController extends HttpServlet {
private HelloModel model = new HelloModel();
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("user"); //收集请求参数
String message = model.doHello(name); //委托HelloModel对象处理
request.setAttribute("message", message); //将结果信息设置到请求对象成为属性
request.getRequestDispatcher("hello.view") //转发给hello.view进行响应
.forward(request, response);
}
}
HelloController会收集请求参数并委托一个HelloModel对象处理,HelloControlller中不会有任何HTML的出现。HelloModel对象处理的结果,会设置为请求对象中的属性,之后呈现画面的Servlet可以从请求对象中取得该属性。接着将请求的响应工作转发给hell.view来负责。
至于HelloModel类的设计很简单,利用一个HashMap,针对不同的用户设置不同的信息 :
Model2 HelloModel.java
package com.example.Model2;
import java.util.HashMap;
import java.util.Map;
public class HelloModel {
private Map<String,String> messages = new HashMap<>();
public HelloModel() {
messages.put("caterpillar","Hello");
messages.put("Justin","Welcom");
messages.put("Momor","Hi");
}
public String doHello(String user){
String message =messages.get(user);
return String.format("%s,%s!",message,user);
}
}
这是一个再简单不过的类。要注意的是,HelloModel对象处理完结果返回给HelloController,
HelloModel类中不会有任何HTML的出现。也没有任何与前端呈现技术或后端存储技术的API出现,是个纯粹的Java对象。
HelloController得到HelloModel对象的返回值后,将流程转发给HelloView呈现画面;
Model2 HelloVIew.java
package com.example.Model2;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello.view")
public class HelloView extends HttpServlet {
private String htmlTemplate =
"<!DOCTYPE html>"
+ "<html>"
+ " <head>"
+ " <meta charset='UTF-8'>"
+ " <title>%s</title>"
+ " </head>"
+ " <body>"
+ " <h1>%s</h1>"
+ " </body>"
+ "</html>";
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("user");
String message = (String) request.getAttribute("message");
String html = String.format(htmlTemplate, user, message);
response.getWriter().print(html);
}
}
在HelloView中分别取得user请求参数以及先前HelloController中设置在请求对象中的message属性。这里特地使用字符串组成HTML样板,在取得请求参数与属性后,分别设置样板中的两个%s 点位符号,然后再输出至浏览器。
可以看到,在Model 2架构的实现下,控制器、视图、模型各司其职,该呈现画面的元件就不会有Java代码出现(HelloView),在负责业务逻辑的元件就不会用HTML输出(HelloModel),该处理请求参数的元件不会牵涉业务逻辑代码(HelloController)
当然,这只是个简单的示范,主要目的在对Model 2 实现有个基本了解。从本章开始,将有个综合练习,以Model 2架构,逐步实现一个功能更完整的应用程序,以便对Model 2架构与实现有更深入的体会。