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请求,但是有时候却看不到(屏蔽了?)