java web----手写tomcat+servlet
1、创建web服务器
Server类(模拟tomcat服务)
public class Server {
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8080);
System.out.println("服务器启动成功");
//解析web.xml
ParseWebXml.parse();
System.out.println("开始解析web.xml");
while (true){
Socket socket = server.accept();
System.out.println(socket);
System.out.println("有一个客户端连接....");
new Thread(new ServerThread(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
class ServerThread implements Runnable{
private Socket socket;
private BufferedWriter bufferedWriter;
private InputStream inputStream;
public ServerThread(Socket socket) throws IOException {
this.socket = socket;
bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
inputStream = socket.getInputStream();
}
@Override
public void run() {
try {
//处理请求,封装请求参数
MyHttpRequest httpRequest = new MyHttpRequest(inputStream);
MyHttpResponse httpResponse = new MyHttpResponse();
String url = httpRequest.getUrl();
// 浏览器会自动请求/favicon.ico,我们给他返回一个图片
if("/favicon.ico".equals(url)){
this.fileReponse();
return;
}
//通过反射创建Servlet对象
String clz = WebContext.getClz(url);
if (clz!=null){
MyServlet servlet = (MyServlet) Class.forName(clz).getConstructor().newInstance();
servlet.service(httpRequest,httpResponse);
}else {
//return 404
httpResponse.sendMessage(bufferedWriter,404);
return;
}
//正文,响应给浏览器的
httpResponse.sendMessage(bufferedWriter,200);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (inputStream!=null){
inputStream.close();
System.out.println("inputStream 已关闭");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void fileReponse(){
//获取图片大小
String fileName = this.getClass().getClassLoader().getResource("favicon.ico").getPath();//获取文件路径
long length = new File(fileName).length();
//使用字节输出流
OutputStream outputStream = null;
try {
outputStream = socket.getOutputStream();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("HTTP/1.1 200 OK\r\n");
stringBuilder.append("Date:").append(new Date()).append("\r\n");
stringBuilder.append("Server:").append("Test Server/0.0.1;charset=GBK").append("\r\n");
stringBuilder.append("Content-type:").append("bytes").append("\r\n");
stringBuilder.append("accept-ranges:").append("image/x-icon").append("\r\n");
stringBuilder.append("Content-length:").append(length).append("\r\n").append("\r\n");
outputStream.write(stringBuilder.toString().getBytes());
InputStream fileInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("favicon.ico");
int len = -1;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ParseWebXml (解析web.xml,相当于java web 中我们配置的web.xml被解析的实现原理)
public class ParseWebXml{
public static void parse() throws ParserConfigurationException, SAXException, IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//创建一个SAX解析器工厂对象;
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
//通过工厂对象创建SAX解析器
SAXParser saxParser = saxParserFactory.newSAXParser();
//创建一个数据处理器(自己实现)
PersonHandle personHandle = new PersonHandle();
//开始解析
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("web.xml");
saxParser.parse(resourceAsStream,personHandle);
personHandle.mappingEntityList.forEach(new Consumer<MappingEntity>() {
@Override
public void accept(MappingEntity mappingEntity) {
System.out.println(mappingEntity.name);
}
});
//personHandle.mappingEntityList.forEach((MappingEntity mappingEntity)->{System.out.println(mappingEntity);});
//personHandle.servletEntityList.forEach((ServletEntity servletEntity)->{System.out.println(servletEntity);});
WebContext.test1(personHandle.mappingEntityList, personHandle.servletEntityList);
//WebContext webContext = new WebContext(personHandle.mappingEntityList, personHandle.servletEntityList);
}
}
class PersonHandle extends DefaultHandler {
public List<MappingEntity> mappingEntityList = null;
public MappingEntity mappingEntity = null;
public List<ServletEntity> servletEntityList = null;
public ServletEntity servletEntity = null;
private String tag; //用来存储当前解析的标签名字
private boolean isMapping = false;
//开始解析文档时调用,只会执行一次
@Override
public void startDocument() throws SAXException {
super.startDocument();
mappingEntityList = new ArrayList<>();
servletEntityList = new ArrayList<>();
System.out.println("开始解析文档.....");
}
//结束解析文档时调用
@Override
public void endDocument() throws SAXException {
super.endDocument();
System.out.println("结束解析文档.....");
}
//每一个标签开始时调用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
//获取每一个标签的person_id属性,如果没有返回null;
//System.out.println(attributes.getValue("person_id"));
if("servlet".equals(qName)){
servletEntity = new ServletEntity();
}
if("servlet-mapping".equals(qName)){
mappingEntity = new MappingEntity();
isMapping = true;
}
tag = qName;
}
//每一个标签结束时调用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if ("servlet".equals(qName)){
servletEntityList.add(servletEntity);
}
if("servlet-mapping".equals(qName)){
mappingEntityList.add(mappingEntity);
isMapping = false;
}
tag=null;
}
//当解析到标签中的内容的时候调用(换行也是文本内容)
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
if(tag!=null){
if (!isMapping){
if ("servlet-name".equals(tag)){
servletEntity.name = new String(ch,start,length);
}
if ("servlet-class".equals(tag)){
servletEntity.className = new String(ch,start,length);
}
}else {
if ("servlet-name".equals(tag)){
mappingEntity.name = new String(ch,start,length);
}
if ("url-pattern".equals(tag)){
mappingEntity.pattern.add(new String(ch,start,length));
}
}
}
}
}
MappingEntiry (封装了 servlet-name和url-pattern),xml解析的时候用这个对象进行封装
public class MappingEntity {
public String name;
public Set<String> pattern = new HashSet<>();
@Override
public String toString() {
return "MappingEntity{" +
"name='" + name + '\'' +
", pattern=" + pattern +
'}';
}
}
ServletEntity(封装了servlet-name和servlet-class),xml解析的时候用这个进行封装
public class ServletEntity {
public String name;
public String className;
@Override
public String toString() {
return "ServletEntity{" +
"name='" + name + '\'' +
", className='" + className + '\'' +
'}';
}
}
WebContext (封装了请求对应的servelet,用于反射来实例化对应的servlet)
public class WebContext {
public static List<MappingEntity> mappingEntityList;
public static HashMap<String,String> mappingEntity_map = new HashMap<>();
public static List<ServletEntity> servletEntityList;
public static HashMap<String,String> servletEntity_map = new HashMap<>();
public static void test1(List<MappingEntity> mappingEntityList, List<ServletEntity> servletEntityList){
servletEntityList.forEach((ServletEntity servletEntity)->{servletEntity_map.put(servletEntity.name,servletEntity.className);});
for(MappingEntity m:mappingEntityList){
for (String str:m.pattern){
mappingEntity_map.put(str,m.name);
}
}
}
public static String getClz(String string){
String s = mappingEntity_map.get(string);
String s1 = servletEntity_map.get(s);
return s1;
}
}
解析请求,并封装请求的信息
public class MyHttpRequest {
private String url;
private InputStream inputStream;
//封装请求参数
private HashMap<String,String> hashMap = new HashMap<>();
//解析参数
public MyHttpRequest(InputStream inputStream) {
byte[] bytes = new byte[1024*10];
System.out.println("等待接收数据");
int len = 0;
try {
len = inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
//如果在读取就会堵塞,原因可能没有读取到一个结束标志,未解决(采取一次性读取)
//int read1 = inputStream.read();
//System.out.println("read1-------"+read1);
//浏览器会发送两个请求(其中有一个请求应该是/favicon.ico),这个/favicon.ico请求有时候读出不了数据返回-1(未解之谜)
if(len!=-1){
String s = new String(bytes, 0, len);
String method = s.substring(0, s.indexOf(" "));
url = s.substring(s.indexOf("/"), s.indexOf(" ",s.indexOf("/")));
//表示有参数
if (url.indexOf("?")!=-1){
String parameter = url.substring(url.indexOf("?")+1, url.lastIndexOf(""));
System.out.println(parameter);
//parameter类似username=1&password=2可以将他存储为HashMap中
String[] split1 = parameter.split("&");
for (String str:split1){
String[] split = str.split("=");
String[] strings = Arrays.copyOf(split, 2);
hashMap.put(strings[0],strings[1]);
}
url = url.substring(0,url.indexOf("?"));
}
// System.out.println(method);
// System.out.println(url);
}
System.out.println(len+"int");
}
public String getParmater(String key){
return hashMap.get(key);
}
public String getUrl(){
return url;
}
}
用户响应信息
public class MyHttpResponse {
private StringBuilder content = new StringBuilder();
private StringBuilder header = new StringBuilder();
//正文长度一定要对,浏览器根据这个大小获得对应的数据.
private int len;
public void createHeader(int code) throws IOException {
switch (code) {
case 200: {
header.append("HTTP/1.1 200 OK\r\n");
break;
}
case 404: {
header.append("HTTP/1.0 404 NOT FOUND\r\n");
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("404.html");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
String msg;
while ((msg = bufferedReader.readLine())!=null){
System.out.println(msg);
content.append(msg);
}
len = content.toString().getBytes().length;
break;
}
case 500: {
header.append("HTTP/1.1 500 SERVER ERROR\r\n");
break;
}
}
//以下消息不是必须的,如果下面的这些信息不写,上面就必须写成stringBuilder.append("HTTP/1.1 200 ok\r\n\n");
header.append("Date:").append(new Date()).append("\r\n");
header.append("Server:").append("Test Server/0.0.1;charset=GBK").append("\r\n");
header.append("Content-type:").append("text/html").append("\r\n");
header.append("Content-length:").append(len).append("\r\n").append("\r\n"); //和正文之间必须有两个换行
}
public void print(String str) {
content.append(str);
len += str.getBytes().length;
}
public void sendMessage(BufferedWriter bufferedWriter, int code) {
try {
this.createHeader(code);
bufferedWriter.write(header.toString());
bufferedWriter.write(content.toString());
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedWriter != null) {
try {
bufferedWriter.close();
System.out.println("bufferedWriter 已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
所有的servlet必须实现这个接口
public interface MyServlet {
public void service(MyHttpRequest myHttpRequest, MyHttpResponse myHttpReponser);
}
LoginServlet
public class LoginServlet implements MyServlet{
@Override
public void service(MyHttpRequest myHttpRequest, MyHttpResponse myHttpReponser) {
myHttpReponser.print("<!DOCTYPE html>");
myHttpReponser.print("<html lang=\"en\">");
myHttpReponser.print("<head>");
myHttpReponser.print("<meta charset=\"UTF-8\">");
myHttpReponser.print("<title>测试</title>");
myHttpReponser.print("</head>");
myHttpReponser.print("<body>");
myHttpReponser.print("<h1>servlet</h1>");
myHttpReponser.print("</body>");
myHttpReponser.print("</html>");
}
}
RegisterServlet
public class RegisterServlet implements MyServlet {
@Override
public void service(MyHttpRequest myHttpRequest, MyHttpResponse myHttpReponser) {
myHttpReponser.print("<!DOCTYPE html>");
myHttpReponser.print("<html lang=\"en\">");
myHttpReponser.print("<head>");
myHttpReponser.print("<meta charset=\"UTF-8\">");
myHttpReponser.print("<title>title</title>");
myHttpReponser.print("</head>");
myHttpReponser.print("<body>");
myHttpReponser.print("<h1>注册成功</h1>");
myHttpReponser.print("</body>");
myHttpReponser.print("</html>");
}
}
resources资源
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
当输入的网址不存在的时候,自动定位到404html页面
</body>
</html>
favicon.ioc
网址https://www.cnblogs.com/favicon.ico 自己下载
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!--配置servlet-->
<servlet>
<!--配置servlet类路径,servlet-name任意,servlet-class是一开始创建的servlet类的路径-->
<servlet-name>ServletDemo</servlet-name>
<servlet-class>com.zy.servlet.LoginServlet</servlet-class>
</servlet>
<!--servlet-name必须和上面的名字一样,url-pattern配置访问的路由 localhost:8080/my-->
<servlet-mapping>
<servlet-name>ServletDemo</servlet-name>
<url-pattern>/</url-pattern>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<!--配置servlet类路径,servlet-name任意,servlet-class是一开始创建的servlet类的路径-->
<servlet-name>RegisterServlet</servlet-name>
<servlet-class>com.zy.servlet.RegisterServlet</servlet-class>
</servlet>
<!--servlet-name必须和上面的名字一样,url-pattern配置访问的路由 localhost:8080/my-->
<servlet-mapping>
<servlet-name>RegisterServlet</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
</web-app>
代码并不完整,只是大体实现功能(可能有一些bug)
git地址 https://github.com/zhengyanzy/Web_Server
上面编程过程中遇到的问题,未解决
1、如果循环recv,一般读取到数据尾部返回-1,但是服务器却会堵塞在最后。
2、在监听开始之后,用浏览器访问服务器,服务器的监听代码如下,accept为阻塞模式的但是总会多余的接收到一个请求(浏览器中发现只有一个请求,但是accept到了),从连接中读取recv返回值为-1(recv会堵塞很长时间,然后返回一个-1),我猜可能是浏览器自带发送/favicon.ico 请求,有时候通过开发者模式确实可以看到浏览器发送了一个/favicon.ico请求,但是有时候却看不到(屏蔽了?)

浙公网安备 33010602011771号