基于Java web服务器简单实现一个Servlet容器

时间:2021-09-30 13:29:44

上篇写了一个简单的Java web服务器实现,只能处理一些静态资源的请求,本篇文章实现的Servlet容器基于前面的服务器做了个小改造,增加了Servlet请求的处理。

 程序执行步骤
 1.创建一个ServerSocket对象;
 2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
 3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
 4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
 5.处理响应(分两种类型,静态资源请求响应或servlet请求响应):如果是静态资源请求,则根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;如果是Servlet请求,则首先生成一个URLClassLoader类加载器,加载请求的servlet类,创建servlet对象,执行service方法(往OutputStream写入响应的数据);
 6.关闭Socket对象;
 7.转到步骤2,继续等待连接请求; 

代码实现:
 添加依赖: 

?
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.3</version>
</dependency>

服务器代码: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package ex02.pyrmont.first;
 
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
 
import ex02.pyrmont.Request;
import ex02.pyrmont.Response;
import ex02.pyrmont.StaticResourceProcessor;
 
public class HttpServer1 {
 
 // 关闭服务命令
 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
 
 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 (true) {
 Socket socket = null;
 InputStream input = null;
 OutputStream output = null;
 try {
 //等待连接,连接成功后,返回一个Socket对象
 socket = serverSocket.accept();
 input = socket.getInputStream();
 output = socket.getOutputStream();
 
 // 创建Request对象并解析
 Request request = new Request(input);
 request.parse();
 // 检查是否是关闭服务命令
 if (request.getUri().equals(SHUTDOWN_COMMAND)) {
  break;
 }
 
 // 创建 Response 对象
 Response response = new Response(output);
 response.setRequest(request);
 
 if (request.getUri().startsWith("/servlet/")) {
  //请求uri以/servlet/开头,表示servlet请求
  ServletProcessor1 processor = new ServletProcessor1();
  processor.process(request, response);
 } else {
  //静态资源请求
  StaticResourceProcessor processor = new StaticResourceProcessor();
  processor.process(request, response);
 }
 
 // 关闭 socket
 socket.close();
 
 } catch (Exception e) {
 e.printStackTrace();
 System.exit(1);
 }
 }
 }
}

常量类: 

?
1
2
3
4
5
6
7
8
9
10
11
package ex02.pyrmont;
 
import java.io.File;
 
public class Constants {
 public static final String WEB_ROOT = System.getProperty("user.dir")
 + File.separator + "webroot";
 public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir")
 + File.separator + "target" + File.separator + "classes";
 
}

Request: 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package ex02.pyrmont;
 
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
 
public class Request implements ServletRequest {
 
 private InputStream input;
 private String uri;
 
 public Request(InputStream input) {
 this.input = input;
 }
 
 public String getUri() {
 return uri;
 }
 /**
 *
 * requestString形式如下:
 * GET /index.html HTTP/1.1
 * Host: localhost:8080
 * Connection: keep-alive
 * Cache-Control: max-age=0
 * ...
 * 该函数目的就是为了获取/index.html字符串
 */
 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;
 }
 //从InputStream中读取request信息,并从request中获取uri值
 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());
 }
 
 /* implementation of the ServletRequest */
 public Object getAttribute(String attribute) {
 return null;
 }
 
 public Enumeration<?> getAttributeNames() {
 return null;
 }
 
 public String getRealPath(String path) {
 return null;
 }
 
 public RequestDispatcher getRequestDispatcher(String path) {
 return null;
 }
 
 public boolean isSecure() {
 return false;
 }
 
 public String getCharacterEncoding() {
 return null;
 }
 
 public int getContentLength() {
 return 0;
 }
 
 public String getContentType() {
 return null;
 }
 
 public ServletInputStream getInputStream() throws IOException {
 return null;
 }
 
 public Locale getLocale() {
 return null;
 }
 
 public Enumeration<?> getLocales() {
 return null;
 }
 
 public String getParameter(String name) {
 return null;
 }
 
 public Map<?, ?> getParameterMap() {
 return null;
 }
 
 public Enumeration<?> getParameterNames() {
 return null;
 }
 
 public String[] getParameterValues(String parameter) {
 return null;
 }
 
 public String getProtocol() {
 return null;
 }
 
 public BufferedReader getReader() throws IOException {
 return null;
 }
 
 public String getRemoteAddr() {
 return null;
 }
 
 public String getRemoteHost() {
 return null;
 }
 
 public String getScheme() {
 return null;
 }
 
 public String getServerName() {
 return null;
 }
 
 public int getServerPort() {
 return 0;
 }
 
 public void removeAttribute(String attribute) {
 }
 
 public void setAttribute(String key, Object value) {
 }
 
 public void setCharacterEncoding(String encoding)
 throws UnsupportedEncodingException {
 }
 
}

Response: 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package ex02.pyrmont;
 
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;
 
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;
 }
 
 //将web文件写入到OutputStream字节流中
 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);
 /*
 * HTTP Response = Status-Line(( general-header | response-header |
 * entity-header ) CRLF) CRLF [ message-body ] Status-Line =
 * HTTP-Version SP Status-Code SP Reason-Phrase CRLF
 */
 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();
 }
 }
 
 /** implementation of ServletResponse */
 public void flushBuffer() throws IOException {
 }
 
 public int getBufferSize() {
 return 0;
 }
 
 public String getCharacterEncoding() {
 return null;
 }
 
 public Locale getLocale() {
 return null;
 }
 
 public ServletOutputStream getOutputStream() throws IOException {
 return null;
 }
 
 public PrintWriter getWriter() throws IOException {
 // autoflush is true, println() will flush,
 // but print() will not.
 writer = new PrintWriter(output, true);
 return writer;
 }
 
 public boolean isCommitted() {
 return false;
 }
 
 public void reset() {
 }
 
 public void resetBuffer() {
 }
 
 public void setBufferSize(int size) {
 }
 
 public void setContentLength(int length) {
 }
 
 public void setContentType(String type) {
 }
 
 public void setLocale(Locale locale) {
 }
}

静态资源请求处理: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ex02.pyrmont;
 
import java.io.IOException;
 
public class StaticResourceProcessor {
 
 public void process(Request request, Response response) {
 try {
 response.sendStaticResource();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
}

Servlet请求处理: 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package ex02.pyrmont.first;
 
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.IOException;
 
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
import ex02.pyrmont.Constants;
import ex02.pyrmont.Request;
import ex02.pyrmont.Response;
 
public class ServletProcessor1 {
 
 public void process(Request request, Response response) {
 
 String uri = request.getUri();
 String servletName = uri.substring(uri.lastIndexOf("/") + 1);
 
 //类加载器,用于从指定JAR文件或目录加载类
 URLClassLoader loader = null;
 try {
 URLStreamHandler streamHandler = null;
 //创建类加载器
 loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler)});
 } catch (IOException e) {
 System.out.println(e.toString());
 }
 
 Class<?> myClass = null;
 try {
 //加载对应的servlet类
 myClass = loader.loadClass(servletName);
 } catch (ClassNotFoundException e) {
 System.out.println(e.toString());
 }
 
 Servlet servlet = null;
 
 try {
 //生产servlet实例
 servlet = (Servlet) myClass.newInstance();
 //执行ervlet的service方法
 servlet.service((ServletRequest) request,(ServletResponse) response);
 } catch (Exception e) {
 System.out.println(e.toString());
 } catch (Throwable e) {
 System.out.println(e.toString());
 }
 
 }
}

Servlet类: 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
 
public class PrimitiveServlet implements Servlet {
 
 public void init(ServletConfig config) throws ServletException {
 System.out.println("init");
 }
 
 public void service(ServletRequest request, ServletResponse response)
 throws ServletException, IOException {
 System.out.println("from service");
 PrintWriter out = response.getWriter();
 out.println("Hello. Roses are red.");
 out.print("Violets are blue.");
 }
 
 public void destroy() {
 System.out.println("destroy");
 }
 
 public String getServletInfo() {
 return null;
 }
 
 public ServletConfig getServletConfig() {
 return null;
 }
 
}

结果测试:
 静态资源请求:

基于Java web服务器简单实现一个Servlet容器

servlet请求(因为只是第一个字符串被刷新到浏览器,所以你不能看到第二个字符串Violets are blue。我们将在后续完善该容器):

基于Java web服务器简单实现一个Servlet容器

改进

前面实现的Servlet容器有一个严重的问题,用户在servlet里可以直接将ServletRequest、ServletResponse向下转 型为Request和Response类型,并直接调用其内部的public方法,这是一个不好的设计,改进方法是给Request、Response 增加外观类,这样,用户只能访问外观类里定义的public方法。
 Request外观类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package ex02.pyrmont.second;
 
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
 
import ex02.pyrmont.Request;
 
public class RequestFacade implements ServletRequest {
 
 private ServletRequest request = null;
 
 public RequestFacade(Request request) {
 this.request = request;
 }
 
 /* implementation of the ServletRequest */
 public Object getAttribute(String attribute) {
 return request.getAttribute(attribute);
 }
 
 public Enumeration<?> getAttributeNames() {
 return request.getAttributeNames();
 }
 
 @SuppressWarnings("deprecation")
 public String getRealPath(String path) {
 return request.getRealPath(path);
 }
 
 public RequestDispatcher getRequestDispatcher(String path) {
 return request.getRequestDispatcher(path);
 }
 
 public boolean isSecure() {
 return request.isSecure();
 }
 
 public String getCharacterEncoding() {
 return request.getCharacterEncoding();
 }
 
 public int getContentLength() {
 return request.getContentLength();
 }
 
 public String getContentType() {
 return request.getContentType();
 }
 
 public ServletInputStream getInputStream() throws IOException {
 return request.getInputStream();
 }
 
 public Locale getLocale() {
 return request.getLocale();
 }
 
 public Enumeration<?> getLocales() {
 return request.getLocales();
 }
 
 public String getParameter(String name) {
 return request.getParameter(name);
 }
 
 public Map<?, ?> getParameterMap() {
 return request.getParameterMap();
 }
 
 public Enumeration<?> getParameterNames() {
 return request.getParameterNames();
 }
 
 public String[] getParameterValues(String parameter) {
 return request.getParameterValues(parameter);
 }
 
 public String getProtocol() {
 return request.getProtocol();
 }
 
 public BufferedReader getReader() throws IOException {
 return request.getReader();
 }
 
 public String getRemoteAddr() {
 return request.getRemoteAddr();
 }
 
 public String getRemoteHost() {
 return request.getRemoteHost();
 }
 
 public String getScheme() {
 return request.getScheme();
 }
 
 public String getServerName() {
 return request.getServerName();
 }
 
 public int getServerPort() {
 return request.getServerPort();
 }
 
 public void removeAttribute(String attribute) {
 request.removeAttribute(attribute);
 }
 
 public void setAttribute(String key, Object value) {
 request.setAttribute(key, value);
 }
 
 public void setCharacterEncoding(String encoding)
  throws UnsupportedEncodingException {
 request.setCharacterEncoding(encoding);
 }
 
}

Response外观类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package ex02.pyrmont.second;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
 
import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;
 
import ex02.pyrmont.Response;
 
public class ResponseFacade implements ServletResponse {
 
 private ServletResponse response;
 
 public ResponseFacade(Response response) {
 this.response = response;
 }
 
 public void flushBuffer() throws IOException {
 response.flushBuffer();
 }
 
 public int getBufferSize() {
 return response.getBufferSize();
 }
 
 public String getCharacterEncoding() {
 return response.getCharacterEncoding();
 }
 
 public Locale getLocale() {
 return response.getLocale();
 }
 
 public ServletOutputStream getOutputStream() throws IOException {
 return response.getOutputStream();
 }
 
 public PrintWriter getWriter() throws IOException {
 return response.getWriter();
 }
 
 public boolean isCommitted() {
 return response.isCommitted();
 }
 
 public void reset() {
 response.reset();
 }
 
 public void resetBuffer() {
 response.resetBuffer();
 }
 
 public void setBufferSize(int size) {
 response.setBufferSize(size);
 }
 
 public void setContentLength(int length) {
 response.setContentLength(length);
 }
 
 public void setContentType(String type) {
 response.setContentType(type);
 }
 
 public void setLocale(Locale locale) {
 response.setLocale(locale);
 }
 
}

处理Servlet请求类: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package ex02.pyrmont.second;
 
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.IOException;
 
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
import ex02.pyrmont.Constants;
import ex02.pyrmont.Request;
import ex02.pyrmont.Response;
 
public class ServletProcessor2 {
 
 public void process(Request request, Response response) {
 
 String uri = request.getUri();
 String servletName = uri.substring(uri.lastIndexOf("/") + 1);
 // 类加载器,用于从指定JAR文件或目录加载类
 URLClassLoader loader = null;
 try {
  URLStreamHandler streamHandler = null;
  // 创建类加载器
  loader = new URLClassLoader(new URL[] { new URL(null, "file:"
   + Constants.WEB_SERVLET_ROOT, streamHandler) });
 } catch (IOException e) {
  System.out.println(e.toString());
 }
 
 Class<?> myClass = null;
 try {
  // 加载对应的servlet类
  myClass = loader.loadClass(servletName);
 } catch (ClassNotFoundException e) {
  System.out.println(e.toString());
 }
 
 Servlet servlet = null;
 //给request、response增加外观类,安全性考虑,防止用户在servlet里直接将ServletRequest、ServletResponse向下转型为Request和Response类型,
 //并直接调用其内部的public方法,因为RequestFacade、ResponseFacade里不会有parse、sendStaticResource等方法;
 RequestFacade requestFacade = new RequestFacade(request);
 ResponseFacade responseFacade = new ResponseFacade(response);
 try {
  servlet = (Servlet) myClass.newInstance();
  servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
 } catch (Exception e) {
  System.out.println(e.toString());
 } catch (Throwable e) {
  System.out.println(e.toString());
 }
 
 }
}

其它代码与前面实现的Servlet容器基本一致。
 验证程序,分别请求静态资源和Servlet,发现结果与前面实现的容器一致;

 参考资料:《深入剖析Tomcat》

@author   风一样的码农

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.cnblogs.com/chenpi/archive/2016/06/21/5603072.html