Q: 为什么需要使用Servlet?
A: Servlet是一种Java编程语言的技术,用于构建动态Web应用程序。它可以处理客户端发送的请求并生成相应的响应。使用Servlet可以实现与数据库的交互、数据处理、用户身份验证等功能。此外,Servlet还具有可扩展性和可重用性,可以轻松地进行开发和维护。因此,使用Servlet可以提升Web应用程序的性能和功能,使其更加强大和灵活。
Q: 如果没有servlet会怎么样?
A: 在没有servlet的情况下,Web应用程序将无法处理动态内容和交互式功能。servlet是一种服务器端的Java程序,用于处理HTTP请求和响应。它可以与数据库进行交互,生成动态的网页内容,并处理用户提交的表单数据。因此,没有servlet的话,Web应用程序将无法实现与用户的交互,无法获取和显示动态数据,也无法与数据库进行通信。这将严重限制Web应用程序的功能和灵活性。
Q: servlet和http协议之间有着什么关联?
A: servlet是一种用于处理HTTP请求和响应的Java技术。它可以被看作是一种在服务器端运行的小程序,通过HTTP协议与客户端进行通信。HTTP协议则是一种用于在网络上传输超文本的协议。它定义了客户端和服务器之间的通信规则和格式。因此,可以说servlet是基于HTTP协议的一种技术,用于实现服务器端的业务逻辑处理和响应客户端的请求。
Q: 为什么说servlet实际上是一个接口呢?
A: servlet是Java编程语言中的一个规范,它定义了一种在Web服务器上运行的组件的行为和功能。简而言之,servlet提供了一种处理HTTP请求和生成HTTP响应的方式。它可以被认为是一个用于构建Web应用程序的基础框架。通过实现servlet接口,开发人员可以编写自己的servlet类,并根据自己的需求来处理客户端的请求和生成响应。因此,servlet实际上是一个接口,它为开发人员提供了一种定义和实现自己的Web应用程序的方式。
此外,Java Servlet API 中还有几个其他重要的组件,用于各种目的,例如处理会话、管理请求和响应头以及处理 URL 映射。这些组件为使用 Java Servlet 技术开发 Web 应用程序提供了强大而灵活的平台。
service()
方法来实现的。接下来我会写两个应用程序去带你们回忆它们:
需要添加一个关于servlet的pom依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
git clone https://github.com/MrKrabXie/writeHttpServer.git
package crab2;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
public class ServletProcessor1 {
public void process(Request request, Response response) {
// 获取请求的uri
String uri = request.getUri();
// 取得uri中"/"后面的字符串作为servletName
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// 创建一个URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// 创建仓库的url
// 这个url创建方法参考了org.apache.catalina.startup.ClassLoaderFactory类中的createClassLoader方法
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
// 生成URL的方法取自org.apache.catalina.loader.StandardClassLoader类中的addRepository方法
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
} catch (IOException e) {
System.out.println(e.toString());
}
// 加载servlet类
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
// 创建servlet实例
servlet = (Servlet) myClass.newInstance();
// 在服务请求和响应上调用servlet
servlet.service((ServletRequest) request, (ServletResponse) response);
} catch (Exception e) {
System.out.println(e.toString());
} catch (Throwable e) {
System.out.println(e.toString());
}
}
}
首先,我们看到类ServletProcessor1,其主要功能是处理Request和Response。在这个类中,一系列的try和catch块尝试创建一个URLClassLoader去加载对应的Servlet并执行服务
这段代码首先从Request中获取URI,然后提取出servletName,即"/"后面的字符串。然后创建一个URLClassLoader,用于加载servlet的类文件。创建ClassLoader需要的repository是从文件系统中获取的WEB_ROOT目录。
经过一系列的加载和实例化后,得到了我们的Servlet实例。然后,将请求和响应进行服务化处理,即servlet.service(request,response)。
package crab2;
import java.io.IOException;
public class StaticResourceProcessor {
// 处理方法,接受请求和响应为参数
public void process(Request request, Response response) {
try {
// 响应发送静态资源
response.sendStaticResource();
}
// 捕获并打印可能出现的IOException异常
catch (IOException e) {
e.printStackTrace();
}
}
}
如上
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
// 实现了 Servlet 接口的服务器组件,可以接收和响应来自客户端的请求
public class PrimitiveServlet implements Servlet {
// 初始化函数,在 Servlet 生命周期中只会被调用一次
public void init(ServletConfig config) throws ServletException {
System.out.println("初始");
}
// service 函数,每次接收到客户端请求时被调用
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
System.out.println("来自 service 函数");
PrintWriter out = response.getWriter();
String errorMessage = "HTTP/1.1 404 文件未找到\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<p>hi rose.<\\p>\r\n" +
"<p>it is blue..<\\p>";
out.println(errorMessage);
}
// 销毁函数,在 Servlet 生命周期结束时被调用
public void destroy() {
System.out.println("销毁");
}
// 返回 Servlet 的信息,此方法可选
public String getServletInfo() {
return null;
}
// 返回 ServletConfig 对象,它包含了 Servlet 的初始化和启动配置
public ServletConfig getServletConfig() {
return null;
}
}
这段代码定义了一个名为PrimitiveServlet的类,并实现了Servlet接口。
init函数是一个初始化函数,Servlet在其生命周期中只调用一次这个函数。service函数在每次接收到客户端请求时被调用。request和response对象分别用来接收和响应客户端的请求。
destroy函数在Servlet的生命周期结束时被调用。
getServletInfo函数返回Servlet的信息,这个方法是可选的。
getServletConfig函数返回一个ServletConfig对象,这个对象包含了Servlet的初始化和启动配置。
第一个应用程序有一个严重的问题。在 ServletProcessor1 类的 process 方法,你向上转换
Request 实例为 javax.servlet.ServletRequest,并作为第一个参数传递给
servlet 的 service 方 法 。 你 也 向 下 转 换Response 实 例 为
javax.servlet.ServletResponse,并作为第二个参数传递给 servlet 的 service 方法。try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) request,(ServletResponse) response); }
这会危害安全性。知道这个 servlet 容器的内部运作的 Servlet 程序员可以分别把
ServletRequest 和 ServletResponse 实 例 向 下 转 换 为 Request 和
Response,并调用他们的公共方法。拥有一个 Request实例,它们就可以调用 parse
方法。拥有一个 Response 实例,就可以调用 sendStaticResource 方法。
这里贴部分代码,解释一下思路就是: 可以通过默认的访问修饰符去控制,也可以通过一个设计模式进行优化:
下面代码,对比他们属性和方法。猜设计模式是什么设计模式。
// 创建公用的请求类
public class Request implements ServletRequest {
// 定义输入流
private InputStream input;
// 定义uri
private String uri;
// 创建构造器,接受输入流参数
public Request(InputStream input) {
this.input = input;
}
// 获取uri的方法
public String getUri() {
return uri;
}
public class RequestFacade implements ServletRequest { private ServletRequest request = null; public RequestFacade(Request request) {
this.request = request;
}
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
是使用门面模式(Facade Pattern)的方法来提升安全性。在最初的设计里,当 ServletRequest 对象被传递给 service 方法时,它会被向下转换为 Request 对象。这样做的问题是 service 方法现在可以调用 Request 中所有的公有方法,包括一些可能导致安全问题的方法。
为了解决这个问题,引入了 RequestFacade 类。这个类实现了 ServletRequest 接口,自身包含一个 Request 的引用,但只暴露出 ServletRequest 接口中的方法,而不需要把 Request 对象本身暴露出来。这样一来,尽管开发者仍可以对 ServletRequest 进行向下转型得到 RequestFacade,但他们只能通过 ServletRequest 接口调用对应的公开方法,无法访问到 Request 类内部的方法。
这样,我们就隐藏了 Request 的内部实现,避免了该类对象的滥用,也就提高了系统的安全性。比如,你提到的 parseUri 方法,就因为它不能从外部访问而变得更安全了。
本章通过构建简单的 Servlet 容器,介绍了 Servlet 编程的基础,为构建完整的 Tomcat 容器奠定了基础。