目录
简介
第二章:一个简单的Servlet容器,涉及到的核心类包括:
- HttpServer:根据url请求的资源将任务分发给ServletProcessor和StaticResourceProcessor
- Request:实现ServletRequest接口,功能扩充
- Response:实现ServletResponse接口,功能扩充
- ServletProcessor:调用Servlet的service方法,新增的类
- StaticResourceProcessor:处理静态资源,新增的类
源码
HttpServer
public class HttpServer {
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer1 server = new HttpServer1();
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
Request request = new Request(input);
request.parse();
Response response = new Response(output);
response.setRequest(request);
// 新增部分: 根据请求的url格式的不同,进行不同的处理
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
和第一章相比,新增部分:根据请求类型来分配任务
Request
public class Request implements ServletRequest {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public String getUri() {
return uri;
}
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
// ... 省略ServletRequest接口中其他的方法
}
实现了ServletRequest接口,但实际上和第一章的Request的功能基本相同
Response
public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
PrintWriter writer;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
/* This method is used to serve a static page */
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
/* request.getUri has been replaced by request.getRequestURI */
File file = new File(Constants.WEB_ROOT, request.getUri());
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
catch (FileNotFoundException e) {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
finally {
if (fis!=null)
fis.close();
}
}
// ... 省略ServletRequest接口中其他的方法
}
实现了ServletResponse接口,但实际上和第一章的Response的功能基本相同
ServletProcessor
public class ServletProcessor {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
// 从本地磁盘指定的地址加载类(反射只能加载程序中存在的类,无法加载本地磁盘或者网络中的类)
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
// 调用Servlet.service()方法
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
}
StaticResourceProcessor
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResource();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
Constants
public class Constants {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
}
UML类图
流程图
图中红色部分是相对于上一章新增的逻辑,增加了对Servlet的调用
运行程序
浏览器地址栏中输入:
- http://localhost:8080/index.html
- http://localhost:8080/servlet/PrimitiveServlet
延伸–设计模式之外观模式
- 现在系统有个问题,我们向Servlet的service方法传入的ServletRequest和ServletResponse如果在service方法中被向下转型成Request和Response,那么Servlet程序员就可以调用Request的parse()方法、Response的sendStaticResource()方法(Servlet程序员负责编写Servlet),就面向对象来说,Request和Response的封装性收到了破坏,因此引入了一种设计模式—外观模式。
- 外观模式,外观模式隐藏了系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,在我看来就是加强了类的封装。
- 除了降低parse()方法和sendStaticResource()方法的访问权限(去掉public,变成只允许包内访问),更好的解决方法是使用外观模式
- 外观模式的UML类图如下:
在外观类RequestFacade中,间接调用Request的方法,且不提供parse()方法,ResponseFacade类同理,代码如下:
public class RequestFacade implements ServletRequest {
private ServletRequest request;
public RequestFacade(Request request) {
this.request = request;
}
// 方法举一个例子,其他方法省略
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
}
在ServletProcessor中调用service方法时,传入的是RequestFacade,而不是Request
// ...省略
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
servlet.service(requestFacade, responseFacade);
} catch (Exception e) {
System.out.println(e.toString());
}
// ...省略
这样一来,在Servlet中只能将Request向下转型为RequestFacade,而RequestFacade中并没有提供parse方法。