DIY-Tomcat part 3 实现对动态资源的请求
实现ServletRequest
package connector;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
/*
GET /index.html HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
*/
public class Request implements ServletRequest {
private static final int BUFFER_SIZE = 1024;
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public String getRequestURI() {
return uri;
}
public void parse() {
int length = 0;
byte[] buffer = new byte[BUFFER_SIZE];
try {
length = input.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
StringBuilder request = new StringBuilder();
for (int j=0; j<length; j++) {
request.append((char)buffer[j]);
}
uri = parseUri(request.toString());
}
private String parseUri(String s) {
int index1, index2;
index1 = s.indexOf(' ');
if (index1 != -1) {
index2 = s.indexOf(' ', index1 + 1);
if (index2 > index1) {
return s.substring(index1 + 1, index2);
}
}
return "";
}
@Override
public Object getAttribute(String s) {
return null;
}
@Override
public Enumeration getAttributeNames() {
return null;
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
}
@Override
public int getContentLength() {
return 0;
}
@Override
public String getContentType() {
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return null;
}
@Override
public String getParameter(String s) {
return null;
}
@Override
public Enumeration getParameterNames() {
return null;
}
@Override
public String[] getParameterValues(String s) {
return new String[0];
}
@Override
public Map getParameterMap() {
return null;
}
@Override
public String getProtocol() {
return null;
}
@Override
public String getScheme() {
return null;
}
@Override
public String getServerName() {
return null;
}
@Override
public int getServerPort() {
return 0;
}
@Override
public BufferedReader getReader() throws IOException {
return null;
}
@Override
public String getRemoteAddr() {
return null;
}
@Override
public String getRemoteHost() {
return null;
}
@Override
public void setAttribute(String s, Object o) {
}
@Override
public void removeAttribute(String s) {
}
@Override
public Locale getLocale() {
return null;
}
@Override
public Enumeration getLocales() {
return null;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String s) {
return null;
}
@Override
public String getRealPath(String s) {
return null;
}
}
- 主要是为了实现ServletRequest 接口
实现ServletResponse
package connector;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.*;
import java.util.Locale;
/*
HTTP/1.1 200 OK
*/
public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
File file = new File(ConnectorUtils.WEB_ROOT, request.getRequestURI());
try {
write(file, HttpStatus.SC_OK);
} catch (IOException e) {
write(new File(ConnectorUtils.WEB_ROOT, "404.html"), HttpStatus.SC_NOT_FOUND);
}
}
private void write(File resource, HttpStatus status) throws IOException {
try (FileInputStream fis = new FileInputStream(resource)) {
output.write(ConnectorUtils.renderStatus(status).getBytes());
byte[] buffer = new byte[BUFFER_SIZE];
int length = 0;
while ((length = fis.read(buffer, 0, BUFFER_SIZE)) != -1) {
output.write(buffer, 0, length);
}
}
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
@Override
public PrintWriter getWriter() throws IOException {
PrintWriter writer = new PrintWriter(output, true);
return writer;
}
@Override
public void setContentLength(int i) {
}
@Override
public void setContentType(String s) {
}
@Override
public void setBufferSize(int i) {
}
@Override
public int getBufferSize() {
return 0;
}
@Override
public void flushBuffer() throws IOException {
}
@Override
public void resetBuffer() {
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public void reset() {
}
@Override
public void setLocale(Locale locale) {
}
@Override
public Locale getLocale() {
return null;
}
}
- 也是主要为了实现ServletRequest 接口
- 实现getWriter()方法
实现Servlet
import connector.ConnectorUtils;
import connector.HttpStatus;
import connector.Request;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
PrintWriter out = servletResponse.getWriter();
out.println(ConnectorUtils.renderStatus(HttpStatus.SC_OK));
out.println("What time is it now?");
out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()));
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
- service实时返回当前时间的具体值
实现ServletProcessor
package processor;
import connector.*;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class ServletProcessor {
URLClassLoader getServletLoader() throws MalformedURLException {
File webroot = new File(ConnectorUtils.WEB_ROOT);
URL webrootUrl = webroot.toURI().toURL();
return new URLClassLoader(new URL[]{webrootUrl});
}
Servlet getServlet(URLClassLoader loader, Request request) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
/*
/servlet/TimeServlet
*/
String uri = request.getRequestURI();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
Class servletClass = loader.loadClass(servletName);
Servlet servlet = (Servlet) servletClass.newInstance();
return servlet;
}
public void process(Request request, Response response) throws IOException {
//加载loader, 以获得具体servlet的上级目录
URLClassLoader loader = getServletLoader();
//通过request中的uri和loader中的上级目录加载对应的具体servlet类处理请求
Servlet servlet = getServlet(loader, request);
//用加载的servlet类处理请求
servlet.service(request, response);
}
主要方法功能解释:
- Class servletClass = loader.loadClass(servletName);
loader会从webrootUrl的路径下尝试加载名字为servletName的类,这是实现请求动态资源的关键 - 用loader和request加载具体的servlet,再用该servlet处理请求,输出内容
改进Connector
改进Connector使其能够处理来自客户端对动态资源的访问请求
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
} else {
StaticProcessor processor = new StaticProcessor();
processor.process(request, response);
}
- 如果请求是以/servlet/开头的,代表是对动态资源的请求,则将该请求交由ServletProcessor处理
- 如果不是以/servlet/开头的,代表是对静态资源的请求,则交由StaticProcessor处理
测试
IDE客户端请求测试
public static void main(String[] args)throws Exception{
Socket socket = new Socket("localhost", 8888);
OutputStream output = socket.getOutputStream();
output.write("GET /servlet/TimeServlet HTTP/1.1".getBytes());
socket.shutdownOutput();
InputStream input = socket.getInputStream();
byte[] buffer = new byte[2048];
int length = input.read(buffer);
StringBuilder response = new StringBuilder();
for (int j=0; j<length; j++) {
response.append((char)buffer[j]);
}
System.out.println(response.toString());
socket.shutdownInput();
socket.close();
}
web浏览器测试
上述两种测试方法均能返回正确结果