前言
最近重新复习了一遍tomcat的简单模拟,为啥是简单的呢?只能实现一些简单的静态页面及完成简单的登录功能。不过登录功能那块并没有展示到这篇博客中。
先了解下HTTP吧
在模拟之前还是有必要看看HTTP的,虽然也涉及到Socket的知识,但是我就当你们都会哈哈,真的不会的话也可以看看我的那篇:利用Java的Socket网络编程实现小型聊天室。
HTTP协议概述
协议是干啥的?协议就是一种约定的规则。网络协议呢?网络协议就是数据在网站上传输的规则比如http、pop3、pop、imap、ftp、流媒体协议等。而HTTP协议的全称是htyper text transform protocal即超文本传输协议:如何在互联网上传输超文本,而超文本是啥呢?超文本就是HTML(超文本标记语言 htyper text markup language)页面。
组成部分
HTTP协议分为请求部分,相应部分。
请求部分
先看下例子(访问我的一篇转载博客时的例子,只截取了少部分):
我复制出来是这样(截取了少部分):
Host: blog.csdn.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: https://editor.csdn.net/md?articleId=105137409
Upgrade-Insecure-Requests: 1
Connection: keep-alive
Cookie: uuid_tt_dd=10_20854976790-1584620675004-265380;
Cache-Control: max-age=0
...
这个是请求头部分,还有请求行和请求体。请求行可以看这个:
这个不是请求行,但是将请求行中数据这样显示出来了。总的来说,请求部分的示例如下:
// 这个是请求行,末尾是\n
GET /CodingNO1/article/details/105137409 HTTP/2
// 接下来是请求头,末尾是\n,最后一行的末尾是\n\n
Host: blog.csdn.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: https://editor.csdn.net/md?articleId=105137409
Upgrade-Insecure-Requests: 1
Connection: keep-alive
Cookie: uuid_tt_dd=10_20854976790-1584620675004-265380;
Cache-Control: max-age=0
// 最后是请求体(注意:请求头和请求体之间有个空行,这也是为什么请求头最后一行是两个换行符)
// 如果是get请求,则请求体是空的,post则请求体中有数据
第一部分是请求行,用来说明请求类型,要访问的资源(GET请求的数据就是在资源路径名后)以及所使用的HTTP版本。
第二部分是请求头部,用来说明服务器要使用的附加信息
第三部分是空行,请求头部后面的空行是必须的,无论第四部分的请求数据是否为空。
第四部分是请求数据也交请求体,可以添加任意长度的其他数据
响应部分
响应部分和请求部分类似,也分为响应行,响应头,响应体。
直接看例子吧:
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>
第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第二部分:消息报头,用来说明客户端要使用的一些附加信息
第三部分:空行,消息报头后面的空行是必须的
第四部分:响应正文,服务器返回给客户端的文本信息。空行后面的html部分为响应正文。
Tomcat的模拟
第一个版本
这个版本代码没有进行封装,全弄到一个类中了,且只能发送静态页面。
package com.yuer.mytomcat;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 模拟服务端
* 这个版本只能发送静态文件
*
* @author Yuer
*
*/
public class TestServer {
// 代表静态资源路径
private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";
// 代表请求的文件路径
private static String url = "";
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream ops = null;
InputStream is = null;
try {
// 第一步,创建ServerSocket对象,监听本机的8080端口号
serverSocket = new ServerSocket(8080);
while (true) {
// 等待来自客户端的请求和获取客户端对象对应的Socket对象
socket = serverSocket.accept();
// 通过获取到的Socket对象获取输出流对象和输入流对象
ops = socket.getOutputStream();
is = socket.getInputStream();
// 获取请求体中的文件路径并赋值给url
parse(is);
// 发送静态资源给客户端
sendStatic(ops);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (null != ops) {
ops.close();
ops = null;
}
// 有可能是这里的输入没有关闭导致请求一直出问题
if (null != is) {
is.close();
is = null;
}
if (null != socket) {
socket.close();
socket = null;
}
}
}
/**
* 获取请求部分中的文件路径并赋值给url
*
* @param is
* @throws IOException
*/
private static void parse(InputStream is) throws IOException {
// 这里读取完的时间是在游览器停止后会导致报错
// int i = 0;
// StringBuffer temp = new StringBuffer();
//
// while ((i = is.read()) != -1) {
// temp.append((char) i);
// }
StringBuffer content = new StringBuffer();
byte[] buffer = new byte[2048];
int i = -1;
i = is.read(buffer);
for (int j = 0; j < i; j++) {
content.append((char) buffer[j]);
}
System.out.println(content.toString());
// 接下来进行请求部分的解析
parseUrl(content.toString());
}
/**
* 将请求部分中的请求行的资源路径解析出来
*
* @param temp
*/
private static void parseUrl(String temp) {
String str = "";
str = temp.substring(temp.indexOf(" ") + 1);
url = str.substring(0, str.indexOf(" "));
System.out.println(url);
}
/**
* 发送静态资源给客户端
*
* @param ops
* @throws IOException
*/
private static void sendStatic(OutputStream ops) {
FileInputStream fis = null;
File file = new File(WEB_ROOT + url);
try {
if (file.exists()) {
// 响应行
ops.write("HTTP/1.1 200 OK\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
int i = 0;
StringBuffer buff = new StringBuffer();
fis = new FileInputStream(file);
while ((i = fis.read()) != -1) {
buff.append((char) i);
}
// 响应体
ops.write(buff.toString().getBytes());
} else {
// 响应行
ops.write("HTTP/1.1 404 NotFound\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
ops.write("file not found".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:这里有个bug,可能需要说下,对于parse()方法:
private static void parse(InputStream is) throws IOException {
// 这里读取完的时间是在游览器停止后会导致报错
// int i = 0;
// StringBuffer temp = new StringBuffer();
//
// while ((i = is.read()) != -1) {
// temp.append((char) i);
// }
StringBuffer content = new StringBuffer();
byte[] buffer = new byte[2048];
int i = -1;
i = is.read(buffer);
for (int j = 0; j < i; j++) {
content.append((char) buffer[j]);
}
System.out.println(content.toString());
// 接下来进行请求部分的解析
parseUrl(content.toString());
}
parse()方法注释的那几行,是我第一次写时用的读取请求部分信息的IO方式,但是由于这个while()中的判断问题,导致出现游览器一直在发请求,但是收不到回复,如果手动关闭请求后,服务端报错:
这里原因是判断有问题,所以有两个解决方式(都不太完美,这是在没有考虑请求体中有数据的前提):
第一个。不用判断即直接使用一个固定长度的字节数组去一次性读取完数据就没问题了,这里为什么不怕没有读取完呢?一般来说,请求部分数据不会超过2KB,所以这里使用长度为2048的字节数组。
第二个。使用判断,这种情况就不能继续使用InputStream,可以使用字符流的判断。
String line = "";
while((line=read.readLine()) != null) {
if ("".equals(line)) { // 说明读到空行了
break;
}
//... 其他处理
}
这种是使用的判断每一行,读到空行代表请求头以及读完了,对于不利用模拟Tomcat进行数据交互的情况下基本够用。
注意:这两种方式都是没有考虑到请求体中有数据,如果请求体中大量数据,则第一种就有可能超过2KB的数据量,第二种根本不会读取这些数据信息。如果有更好的解决方式,请联系我一下,感激不尽-.-
第二个版本
这里通过读取配置文件中的请求信息进行反射获取Servlet(自定义的),实现了一定程度上的动态哈哈。
配置文件:
先看下Servlet接口:
package com.yuer.mytomcat.dymatic;
import java.io.InputStream;
import java.io.OutputStream;
public interface Servlet {
public void init();
public void service(InputStream is, OutputStream ops);
public void destroy();
}
两个实现类:
AA:
package com.yuer.mytomcat.dymatic;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class AAServlet implements Servlet {
public void init() {
// TODO Auto-generated method stub
}
public void service(InputStream is, OutputStream ops) {
try {
ops.write("我是AA Servlet".getBytes());
ops.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void destroy() {
// TODO Auto-generated method stub
}
}
BB:
package com.yuer.mytomcat.dymatic;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BBServlet implements Servlet {
public void init() {
// TODO Auto-generated method stub
}
public void service(InputStream is, OutputStream ops) {
try {
ops.write("我是BB Servlet".getBytes());
// 这里不确定是否要刷新
ops.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void destroy() {
// TODO Auto-generated method stub
}
}
服务端:
package com.yuer.mytomcat.dymatic;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* 模拟服务端
* 这个版本可以发送静态和动态,下个版本进行一些封装
*
* @author Yuer
*
*/
@SuppressWarnings("rawtypes")
public class Server {
// 代表静态资源路径
private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";
// 代表请求的文件路径
private static String url = "";
// 还要将配置文件中的信息读取到map中
private static Map<String, String> map = new HashMap<String, String>();
// 服务器启动之前初始化配置信息到map中
static {
// 使用javaAPI读取配置文件信息
Properties prop = new Properties();
try {
// 先加载
prop.load(new FileInputStream(WEB_ROOT + "\\conf.properties"));
// 将配置文件中的数据读取到map中、
Set set = prop.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
String value = prop.getProperty(key);
map.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream ops = null;
InputStream is = null;
try {
// 第一步,创建ServerSocket对象,监听本机的8080端口号
serverSocket = new ServerSocket(8080);
while (true) {
// 等待来自客户端的请求和获取客户端对象对应的Socket对象
socket = serverSocket.accept();
// 通过获取到的Socket对象获取输出流对象和输入流对象
ops = socket.getOutputStream();
is = socket.getInputStream();
// 获取请求体中的文件路径并赋值给url
parse(is);
// 先判断是否是/或没有 则直接访问web.xml中的欢迎页
// 这里还没做。。。。
// 判断本次请求的是静态页面还是servlet程序
if (null != url) {
// 看是否存在. 即是否访问静态
if (url.indexOf(".") != -1) {
// 发送静态资源给客户端
sendStatic(ops);
} else {
// 发送动态的
sendDynamic(is, ops);
}
}
// 关闭会立马释放资源,设置为null,只是说明我不再指向这个对象
socket.close();
// socket = null;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (null != ops) {
ops.close();
ops = null;
}
// 有可能是这里的输入没有关闭导致请求一直出问题
if (null != is) {
is.close();
is = null;
}
if (null != socket) {
socket.close();
socket = null;
}
}
}
/**
* 获取请求部分中的文件路径并赋值给url
*
* @param is
* @throws IOException
*/
private static void parse(InputStream is) throws IOException {
StringBuffer content = new StringBuffer();
byte[] buffer = new byte[2048];
int i = -1;
i = is.read(buffer);
for (int j = 0; j < i; j++) {
content.append((char) buffer[j]);
}
// 做测试
System.out.println(content.toString());
// 接下来进行请求部分的解析
parseUrl(content.toString());
}
/**
* 将请求部分中的请求行的资源路径解析出来
*
* @param temp
*/
private static void parseUrl(String temp) {
String str = "";
// 这里没考虑啥都没有的情况,即localhost:8080 或local host:8080/(游览器自动省略/)
str = temp.substring(temp.indexOf(" ") + 2);
url = str.substring(0, str.indexOf(" "));
// 做测试
System.out.println(url);
System.out.println(url + "-----");
}
/**
* 发送动态资源
* @param ops
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ClassNotFoundException
*/
private static void sendDynamic(InputStream is, OutputStream ops) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
// 接下来判断这次请求的url是否存在于map中
if (map.containsKey(url)) {
// 先发送响应行和响应头
// 响应行
ops.write("HTTP/1.1 200 OK\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
// 如果包含指定的key,获取到map中key对应的value部分
String value = map.get(url);
// 通过反射调用对应的Servlet 这里后面通过web.xml或者注解
Class clazz = Class.forName(value);
Servlet servlet = (Servlet) clazz.newInstance();
// 执行init方法
servlet.init();
// 执行service
servlet.service(is, ops);
// 执行销毁 销毁不知道是在什么时候
// servlet.destroy();
} else {
// 响应行
ops.write("HTTP/1.1 404 NotFound\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
ops.write("reuqest is bad".getBytes());
}
}
/**
* 发送静态资源给客户端
*
* @param ops
* @throws IOException
*/
private static void sendStatic(OutputStream ops) {
FileInputStream fis = null;
File file = new File(WEB_ROOT,url);
try {
if (file.exists()) {
// 响应行
ops.write("HTTP/1.1 200 OK\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
int i = 0;
StringBuffer buff = new StringBuffer();
fis = new FileInputStream(file);
while ((i = fis.read()) != -1) {
buff.append((char) i);
}
// 响应体
ops.write(buff.toString().getBytes());
} else {
// 响应行
ops.write("HTTP/1.1 404 NotFound\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
ops.write("file not found".getBytes());
}
ops.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
第三版本
这里对每个逻辑都进行了封装,将请求路径对应的配置信息弄到类似于my.xml中了,还有默认访问的界面也写在配置文件my.xml中了。且将每一个客户端即游览器访问的请求封装为一个线程了,再在服务端使用了线程池进行了管理。
相关文件
my.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet-mapping>
<servlet-name>AAServlet</servlet-name>
<servlet-class>com.yuer.mytomcat.dymatic.pro.AAServlet</servlet-class>
<url-pattern>aa</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BBServlet</servlet-name>
<servlet-class>com.yuer.mytomcat.dymatic.pro.BBServlet</servlet-class>
<url-pattern>bb</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>demo1.html</welcome-file>
</welcome-file-list>
</web-app>
application.propeties(设置端口之类的信息):
#配置端口
port=8080
处理properties和xml配置文件的两个类:
parseXml.java:
package com.yuer.mytomcat.dymatic.pro;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* 这里使用jsoup解析xml
*
* @author Yuer
*
*/
public class ParseXml {
// 还要将配置文件中的信息读取到map中
private static Map<String, String> map = new HashMap<String, String>();
// 这里将配置文件中的欢迎页读取出来
private static String welcome = "";
// 使用之前就初始化好了
static {
Document doc;
Elements as;
try {
doc = Jsoup.parse(new File("src/main/webapp/my.xml"), "utf-8");
// 先将欢迎页初始化
Element ele = doc.getElementsByTag("welcome-file").first();
welcome = ele.text();
// 再将请求与类路径进行映射
// 获取Elements的方式有很多种:根据id,class,tag,属性(好像是看是否存在这个属性)都可
as = doc.getElementsByTag("servlet-mapping");
for (Element e : as) {
// 这个是请求路径
Element a = e.child(2);
Element b = e.child(1);
map.put(a.text(), b.text());
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
public static Map<String, String> getMap() {
return map;
}
public static void setMap(Map<String, String> map) {
ParseXml.map = map;
}
public static String getWelcome() {
return welcome;
}
public static void setWelcome(String welcome) {
ParseXml.welcome = welcome;
}
}
ReadConfig:
package com.yuer.mytomcat.dymatic.pro;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 读取properties配置文件
* @author Yuer
*
*/
public class ReadConfig extends Properties{
private static final long serialVersionUID = 4896442440078075125L;
private static ReadConfig instance = new ReadConfig();
private ReadConfig() {
try(InputStream is = this.getClass().getClassLoader().getResourceAsStream("application.properties");) {
this.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static ReadConfig getInstance() {
return instance;
}
}
Servlet
Servlet和第二版本的一致,就不重复粘贴了。
Request
package com.yuer.mytomcat.dymatic.pro;
import java.io.IOException;
import java.io.InputStream;
/**
* 获取HTTP的请求头所有信息并截取URL地址返回
*
* @author Yuer
*
*/
public class Request {
// 代表请求的文件路径
private String url = "";
private InputStream is = null;
public Request() {
}
public Request(InputStream is) {
this.is = is;
try {
parse();//解析请求
} catch (IOException e) {
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getIs() {
return is;
}
public void setIs(InputStream is) {
this.is = is;
}
/**
* 获取请求部分中的文件路径并赋值给url
*
* @param is
* @throws IOException
*/
public void parse() throws IOException {
// 这里读取完的时间是在游览器停止后会导致报错
// 这里read是返回下一个字节,而read(byte[] b)则是返回读入缓冲区b的总字节数
// int i = 0;
// StringBuffer temp = new StringBuffer();
//
// while ((i = is.read()) != -1) {
// temp.append((char) i);
// }
/*OutputStream out = new BufferedOutputStream(response.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = in.read(bytes)) != -1) {
out.write(bytes, 0, len);
}
in.close();
out.flush();
out.close();*/
StringBuffer content = new StringBuffer();
// 这里为什么是2048且一次性读取完(不怕文件大读不完?这里不是读文件而是请求信息)
// get 是通过URL提交数据,因此GET可提交的数据量就跟URL所能达到的最大长度有直接关系。很多文章都说GET方式提交的数据最多只能是1024字节,而 实际上,URL不存在参数上限的问题,HTTP协议规范也没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制 是2083字节(2K+35字节)。对于其他浏览器,如FireFox,Netscape等,则没有长度限制,
// 这个时候其限制取决于服务器的操作系统。即 如果url太长,服务器可能会因为安全方面的设置从而拒绝请求或者发生不完整的数据请求。
// 所以一般而言请求体的信息2048应该够了,直接一次读取即可
byte[] buffer = new byte[2048];
int i = -1;
i = is.read(buffer);
for (int j = 0; j < i; j++) {
content.append((char) buffer[j]);
}
// 做测试
System.out.println(content.toString());
// 接下来进行请求部分的解析
parseUrl(content.toString());
}
/**
* 将请求部分中的请求行的资源路径解析出来
*
* @param temp
*/
private void parseUrl(String temp) {
String str = "";
// 这里没考虑啥都没有的情况,即localhost:8080 或local host:8080/(游览器自动省略/)
str = temp.substring(temp.indexOf(" ") + 2);
url = str.substring(0, str.indexOf(" "));
// 做测试
System.out.println(url);
System.out.println(url + "-----");
}
}
Response
package com.yuer.mytomcat.dymatic.pro;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import com.yuer.mytomcat.util.CloseAll;
/**
* 响应请求读取文件并写回到浏览器
*
* @author Yuer
*
*/
@SuppressWarnings("rawtypes")
public class Response {
private OutputStream ops = null;
private Request request = null;
// 代表静态资源路径
private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";
// 还要将配置文件中的信息读取到map中
private Map<String, String> map = new HashMap<String, String>();
public Response() {
}
public Response(Request request, OutputStream ops) {
this.request = request;
this.ops = ops;
this.map = ParseXml.getMap();
}
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public OutputStream getOps() {
return ops;
}
public void setOps(OutputStream ops) {
this.ops = ops;
}
/**
* 重定向
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ClassNotFoundException
*/
public void sendRedirect() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
String url = request.getUrl();
// 先判断是否是/或没有 则直接访问my.xml中的欢迎页
if (url == null || "".equals(url)) {
sendStatic(true);
} else { // 判断本次请求的是静态页面还是servlet程序
// 看是否存在. 即是否访问静态
if (url.indexOf(".") != -1) {
// 发送静态资源给客户端
sendStatic(false);
} else {
// 发送动态的
sendDynamic();
}
}
}
/**
* 发送动态资源
*
* @param ops
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ClassNotFoundException
*/
public void sendDynamic()
throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
String url = request.getUrl();
// 接下来判断这次请求的url是否存在于map中
if (map.containsKey(url)) {
// 先发送响应行和响应头
// 响应行
ops.write("HTTP/1.1 200 OK\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行(响应头或请求头到后面的数据部分数据之前必须有空的一行,所以这里加了一个\n表示一个空行)
ops.write("\n".getBytes());
// 如果包含指定的key,获取到map中key对应的value部分
String value = map.get(url);
// 通过反射调用对应的Servlet 这里后面通过web.xml或者注解
Class clazz = Class.forName(value);
Servlet servlet = (Servlet) clazz.newInstance();
// 执行init方法
servlet.init();
// 执行service
servlet.service(request, this);
// 执行销毁 销毁不知道是在什么时候
// servlet.destroy();
} else {
// 响应行
ops.write("HTTP/1.1 404 NotFound\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
ops.write("reuqest is bad".getBytes());
}
}
/**
* 发送静态资源给客户端
* @param flag 代表是否是默认
*/
public void sendStatic(Boolean flag) {
String url = "";
// 是默认即地址栏啥都没写
if (flag) {
// 读取欢迎页
url = ParseXml.getWelcome();
} else {
url = request.getUrl();
}
FileInputStream fis = null;
File file = new File(WEB_ROOT, url);
try {
if (file.exists()) {
// 响应行
ops.write("HTTP/1.1 200 OK\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
int i = 0;
StringBuffer buff = new StringBuffer();
fis = new FileInputStream(file);
while ((i = fis.read()) != -1) {
buff.append((char) i);
}
// 响应体
ops.write(buff.toString().getBytes());
} else {
// 响应行
ops.write("HTTP/1.1 404 NotFound\n".getBytes());
// 响应头
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("Server:Yuer\n".getBytes());
// 响应体上的换行
ops.write("\n".getBytes());
ops.write("file not found".getBytes());
}
// 这里不确定是否要刷新
ops.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
CloseAll.close(fis);
}
}
}
HttpSession
package com.yuer.mytomcat.dymatic.pro;
import java.net.Socket;
/**
* 用来作为线程封装每个游览器访问的动作
* @author Yuer
*
*/
public class HttpSession implements Runnable{
private Socket socket = null;
public HttpSession(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
Request request;
Response response;
try {
request = new Request(socket.getInputStream());
response = new Response(request, socket.getOutputStream());
response.sendRedirect();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Server
package com.yuer.mytomcat.dymatic.pro;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.yuer.mytomcat.util.CloseAll;
/**
* 模拟服务端
* 这个版本可以发送静态和动态,并将一些操作封装到request和response
*
* @author Yuer
*
*/
public class Server {
public static void main(String[] args) throws IOException {
new Server().start();
}
private void start() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
Integer port = Integer.parseInt(ReadConfig.getInstance().getProperty("port"));
// 第一步,创建ServerSocket对象
serverSocket = new ServerSocket(port);
//创建一个线程池,大小为20
ExecutorService service = Executors.newFixedThreadPool(20);
while (true) {
// 等待来自客户端的请求和获取客户端对象对应的Socket对象
socket = serverSocket.accept();
service.execute(new HttpSession(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
CloseAll.close(socket);
}
}
}