手写简单的web服务器
一、用到的知识
oop,容器,io,多线程,网络编程,xml解析,反射,HTML,http
1.反射
将Java类中的各种结构映射成一个个Java对象,利用反射对一个类进行解剖,反射是框架设计灵魂
jdk9 用反射创建对象不再用.newInstance()创建对象,而是getConstructor().newInstance();
clz = Class.forName("包名.类名");//创建对象clz.newInstance();//9,不再这样用clz.getConstructor().newInstance();
2.xml解析
XML 可扩展标记语言 树结构
这里用SAX解析
{ //1、获取解析工厂 SAXParserFactory factory=SAXParserFactory.newInstance(); //2、从解析工厂获取解析器 SAXParser parse =factory.newSAXParser(); //3、编写处理器 //4、加载文档 Document 注册处理器 PHandler handler=new PHandler(); //5、解析 parse.parse(Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/sxt/server/basic/p.xml") ,handler);} class PersonHandler extends DefaultHandler{ private List<Person> persons ; private Person person ; private String tag; //存储操作标签 @Override public void startDocument() throws SAXException { persons = new ArrayList<Person>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(null!=qName) { tag = qName; //存储标签名 if(tag.equals("person")) { person = new Person(); } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { String contents = new String(ch,start,length).trim(); if(null!=tag) { //处理了空 if(tag.equals("name")) { person.setName(contents); }else if(tag.equals("age")) { if(contents.length()>0) { person.setAge(Integer.valueOf(contents)); } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName) { if(qName.equals("person")) { persons.add(person); } } tag = null; //tag丢弃了 } @Override public void endDocument() throws SAXException { } public List<Person> getPersons() { return persons; }}
解析web.xml 文件
文件的解析,用SAX解析,而通过url找到相应的类,就需要创建一个Context类,将集合转化为Map集合,通过键值对的方式找到对应的servlet类
<web-app> <servlet> <servlet-name>Login</servlet-name> <servlet-class>com.yn.server01.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Login</servlet-name> <url-pattern>/login</url-pattern> <url-pattern>/login02</url-pattern> </servlet-mapping> <servlet> <servlet-name>index</servlet-name> <servlet-class>com.yn.server01.IndexServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>index</servlet-name> <url-pattern>/index</url-pattern> </servlet-mapping></web-app>
封装对应的类
public class Entity { private String name; private String clz; ......} public class Mapping { private String name ; private Set<String > patterns; public void addPattern(String pattern){ this.patterns.add(pattern); } ...... }
创建webContext类,将list集合转化为Map集合
public class WebContext { private Map<String,String > entityMap=new HashMap<String,String>(); private Map<String,String> mappingMap=new HashMap<String,String>(); private List<Entity> entitys=null; private List<Mapping> mappings=null; public WebContext(List<Entity> entitys, List<Mapping> mappings) { this.entitys = entitys; this.mappings = mappings; //将entity集合转化为Map集合 for (Entity entity : entitys) { entityMap.put(entity.getName(), entity.getClz()); } //将mapping集合转化为Map集合 for (Mapping mapping : mappings) { for (String pattern : mapping.getPatterns()) { mappingMap.put(pattern, mapping.getName()); } } } /** * 通过url路径找到对应的class文件 * @param pattern * @return */ public String getClz(String pattern){ String name=mappingMap.get(pattern); return entityMap.get(name); }}
测试类
public class XmlTest02 { public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { //1、获取解析工厂 SAXParserFactory factory=SAXParserFactory.newInstance(); //2、从解析工厂获取解析器 SAXParser parse =factory.newSAXParser(); //3、编写处理器 //4、加载文档 Document 注册处理器 PHandler handler=new PHandler(); //5、解析 parse.parse(Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/yn/server01/web.xml") ,handler); //获取解析后的list集合 List<Entity> entitys = handler.getEntitys(); List<Mapping> mappings = handler.getMappings(); //将list集合转化为Map集合 WebContext wc=new WebContext(entitys, mappings); //通过url找到对应的class类名 String clz = wc.getClz("/login02"); try { Class<?> className = Class.forName(clz); Servlet s = (Servlet) className.newInstance(); s.service(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }}class WebHandler extends DefaultHandler{ private List<Entity> entitys ; private List<Mapping> mappings ; private Entity entity; private Mapping mapping; private boolean isMapping=false ; private String tag; // @Override public void startDocument() throws SAXException { entitys =new ArrayList<Entity>(); mappings =new ArrayList<Mapping>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(null!=qName) { tag = qName; // if(tag.equals("servlet")) { entity = new Entity(); isMapping = false; }else if (tag.equals("servlet-mapping")) { mapping=new Mapping(); isMapping=true; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { String contents = new String(ch,start,length).trim(); if(null!=tag) { //处理空 if (isMapping) {//操作servlet-mapping if(tag.equals("servlet-name")) { mapping.setName(contents); }else if(tag.equals("url-pattern")) { if(contents.length()>0) { mapping.addPattern(contents); } } }else{ if(tag.equals("servlet-name")) { entity.setName(contents); }else if(tag.equals("servlet-class")) { if(contents.length()>0) { entity.setClz(contents); } } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName) { if(qName.equals("servlet")) { entitys.add(entity); }else if(qName.equals("servlet-mapping")) { mappings.add(mapping); } } tag = null; //tag丢弃了 } public List<Entity> getEntitys() { return entitys; } public List<Mapping> getMappings() { return mappings; } }
3.HTML
超文本标记语言
post和get请求的区别,
post 提交 基于http协议不同,量大,参数不可见,安全
get 默认 获取,基于http协议不同,量小,参数可见,不安全
表单的name属性是提供给后台使用的,id作为前端使用的
4.Http协议
超文本传输协议,所有的www文件都必须遵守这个标准
http是应用层的协议 tcp和udp是传输层的协议
请求协议
-
请求行
-
请求头
-
请求正文
相应协议
1.状态行
-
响应头
-
响应正文
二、手写服务器
客户端就不用写了,就是浏览器,现在要写的是服务器的内容
1.获取请求协议
使用ServerSocket获取请求协议
/** * 使用ServerSocket建立与浏览器的连接,获取请求协议 * @author student * */public class Server01 { private ServerSocket ss; public static void main(String[] args) { Server01 s=new Server01(); s.start(); } //启动服务 public void start(){ try { ss=new ServerSocket(8888); System.out.println("服务器启动了"); receive(); } catch (IOException e) { e.printStackTrace(); System.out.println("服务器启动失败"); } } //接受连接 public void receive(){ try { Socket client = ss.accept(); System.out.println("一个客户端建立了连接"); //获取请求协议 InputStream is = client.getInputStream(); byte[] datas=new byte[1024*1024]; int len = is.read(datas); String requstInfo=new String(datas,0,len); System.out.println(requstInfo); } catch (IOException e) { e.printStackTrace(); System.out.println("客户端连接出现错误"); } } //停止服务 public void stop(){ }}
2. 获取响应协议
动态的添加内容
累加字节数的长度
根据状态码拼接响应头协议 (注意空格和换行)
使用输出流输出
根据状态码同一推送出去
//获取响应协议 StringBuilder content =new StringBuilder(); content.append("<html>"); content.append("<head>"); content.append("<title>"); content.append("服务器响应成功"); content.append("</title>"); content.append("</head>"); content.append("<body>"); content.append("shsxt server终于回来了。。。。"); content.append("</body>"); content.append("</html>"); int size = content.toString().getBytes().length; //必须获取字节长度 StringBuilder responseInfo =new StringBuilder(); String blank =" "; String CRLF = "\r\n"; //返回 //1、响应行: HTTP/1.1 200 OK responseInfo.append("HTTP/1.1").append(blank); responseInfo.append(200).append(blank); responseInfo.append("OK").append(CRLF); //2、响应头(最后一行存在空行): /* Date:Mon,31Dec209904:25:57GMT Server:shsxt Server/0.0.1;charset=GBK Content-type:text/html Content-length:39725426 */ responseInfo.append("Date:").append(new Date()).append(CRLF); responseInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF); responseInfo.append("Content-type:text/html").append(CRLF); responseInfo.append("Content-length:").append(size).append(CRLF); responseInfo.append(CRLF); //3、正文 responseInfo.append(content.toString()); //写出到客户端 BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); bw.write(responseInfo.toString()); bw.flush();
3.封装response
响应分为响应行,响应头,响应体
响应头中要关注状态码200,404,500
响应头不变
响应体自己写
整个Response类整体分四部分
1.初始化
在无参构造器中将协议头和正文初始化,有参构造器可以传入参数客户端或输出流,初始化
BufferedWriter
2. 构建响应行和响应头
`响应行: HTTP/1.1 200 OK`
响应行中要对状态码进行判断,回应不同的信息
响应头正常添加
3. 响应体
响应体要动态添加,同时字节码长度也要添加
4. 将相应推送出去
用BufferedWriter将内容推送出去
public class Response { private BufferedWriter bw ; //正文 private StringBuilder content; //协议头信息 private StringBuilder headInfo ; private final String BLANK =" "; private final String CRLF = "\r\n"; private int len;//正文的字节数 private Response(){ content=new StringBuilder(); headInfo=new StringBuilder(); len=0; } public Response(Socket client){ this(); try { bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); } catch (IOException e) { e.printStackTrace(); headInfo=null; } } public Response(OutputStream os){ this(); bw =new BufferedWriter(new OutputStreamWriter(os)); } //动态添加内容 public Response print(String info){ content.append(info); len+=info.getBytes().length; return this; } public Response println(String info){ content.append(info).append(CRLF); len+=(info+CRLF).getBytes().length; return this; } //推送响应信息 public void pushToBrowser(int code ){ if (null==headInfo) { code=500; } creatHeadInfo(code); try { bw.append(headInfo); bw.append(content); bw.flush(); } catch (IOException e) { e.printStackTrace(); } } //构建头信息 private void creatHeadInfo(int code){ //1、响应行: HTTP/1.1 200 OK headInfo.append("HTTP/1.1").append(BLANK); headInfo.append(code).append(BLANK); headInfo.append("OK").append(CRLF); switch (code) { case 200: headInfo.append("OK").append(CRLF); break; case 404: headInfo.append("Not Found").append(CRLF); break; case 500: headInfo.append("Server Error").append(CRLF); break; } //2、响应头(最后一行存在空行): /* Date:Mon,31Dec209904:25:57GMT Server:shsxt Server/0.0.1;charset=GBK Content-type:text/html Content-length:39725426 */ headInfo.append("Date:").append(new Date()).append(CRLF); headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF); headInfo.append("Content-type:text/html").append(CRLF); headInfo.append("Content-length:").append(len).append(CRLF); headInfo.append(CRLF); }}
4.封装request
封装了五个属性 协议信息,请求方式,请求的url,请求参数,存储参数
1.构造器初始化
在构造器中初始化parameterMap ,并获取协议信息,之后分析协议信息
2.解析协议信息
通过字符串的分割获取想要的信息,字符串去空要注意,其中解析参数复杂了一点
参数的提交分为post和get请求
当是get请求的时候只需要分析请求头中的参数列表
当是post请求的时候,参数列表在请求头和请求体中都有
3. 将请求参数信息转化为map 集合
将获取的请求参数字符串分割加入Map集合
在获取参数的时候,参数的值为中文的时候回产生乱码,就需要处理乱码的问题
4.处理乱码
调用java.net.URLDecoder.decode(value,enc)方法来处理乱码
5.代码
public class Request02 { //协议信息 private String requstInfo; //请求方式 private String method; //请求url private String url; //请求参数 private String queryStr; //存储参数 private Map<String ,List<String>> parameterMap; private final String CRLF = "\r\n"; public Request02(InputStream is){ parameterMap =new HashMap<String ,List<String>>(); byte[] datas=new byte[1024*1024]; int len; try { len = is.read(datas); this.requstInfo=new String(datas,0,len); System.out.println(requstInfo); } catch (IOException e) { e.printStackTrace(); return ; } //分解字符串 parseRequestInfo(); } public Request02(Socket client) throws IOException{ this(client.getInputStream()); } private void parseRequestInfo(){ System.out.println("-----分解-------"); System.out.println("-----1、获取请求方式 开头第一个/ ------"); this.method=this.requstInfo.substring(0, this.requstInfo.indexOf("/")).toUpperCase(); System.out.println("-----1、获取请求url 开头第一个/到HTTP/------"); System.out.println("-----可能包含的请求参数 前面的为url------"); //获取第一个/ int startIdx=this.requstInfo.indexOf("/")+1; //获取HTTP/的位置 int endIdx=this.requstInfo.indexOf("HTTP/"); //分割字符串 this.url = this.requstInfo.substring(startIdx, endIdx); //获取?的位置 int queryIdx = this.url.indexOf("?"); if (queryIdx>=0) {//表示存在请求参数 String [] urlArray=this.url.split("\\?"); this.url=urlArray[0]; queryStr=urlArray[1]; } System.out.println("-----获取请求参数,如果是GET已经获取,如果是post可能在请求体中------"); this.queryStr=this.queryStr.trim(); this.method=this.method.trim(); if (method.equals("POST")) { String qstr=this.requstInfo.substring(this.requstInfo.lastIndexOf(CRLF)).trim(); System.out.println("---->"+qstr); if (null==queryStr) { queryStr=qstr; }else { queryStr+="&"+qstr; } } queryStr= null==queryStr?"":queryStr; //this.queryStr=this.queryStr.trim(); //System.out.println("url :"+this.url+" method :"+method +" queryStr :"+queryStr); System.out.println(queryStr); //转成Map convertMap(); } //处理请求参数为Map private void convertMap(){ //分割字符串 String[] keyValues=this.queryStr.split("&"); for (String queryStr : keyValues) { //再次分割字符串 = String[] kv=queryStr.split("="); kv = Arrays.copyOf(kv, 2); //获取key和value String key=kv[0]; String value=kv[1]==null?null:decode(kv[1], "utf-8"); //存放到Map中 if (!parameterMap.containsKey(key)) { parameterMap.put(key, new ArrayList<String>()); } parameterMap.get(key).add(value); } } private String decode(String value,String enc){ try { return java.net.URLDecoder.decode(value,enc); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 通过name获取对应的多个值 * @param key * @return */ public String[] getPatameterValues(String key){ List<String> values = this.parameterMap.get(key); if (null==values||values.size()<0) { return null; } return values.toArray(new String[0]); } /** * 通过name获取对应的一个值 * @param key * @return */ public String getPatameter(String key){ String[] values = getPatameterValues(key); return values==null?null:values[0]; } public String getMethod() { return method; } public String getUrl() { return url; } public String getQueryStr() { return queryStr; } }
5.引入Servlet
创建Servlet父类,定义service方法方法需要传入参数Request和Response对象,在子类中的service方法中,写具体的响应内容response.print(),Server类中判断请求地址url,调用对应的servlet,调用service方法,在页面响应
//接受连接 public void receive(){ try { Socket client = ss.accept(); System.out.println("一个客户端建立了连接"); //获取请求协议 Request request=new Request(client); //获取响应协议 Response response=new Response(client); Servlet servlet = null; if (request.getUrl().equals("login")) { servlet=new LoginServlet(); }else if (request.getUrl().equals("index")) { servlet=new IndexServlet(); } servlet.service(request, response); response.pushToBrowser(200); } catch (IOException e) { e.printStackTrace(); System.out.println("客户端连接出现错误"); } }
6.整和web.xml 文件
将之前的web.xml解析的代码拿过来,创建WebApp 和 WebHandler类 解析配置文件,定义方法用来通过url获取配置文件的servlet,WebHandler类,是用来将解析的内容封装到对应的类
webApp类
public class WebApp { private static WebContext wc; static { SAXParserFactory factory=SAXParserFactory.newInstance(); //2銆佷粠瑙f瀽宸ュ巶鑾峰彇瑙f瀽鍣� SAXParser parse; try { parse = factory.newSAXParser(); //3銆佺紪鍐欏鐞嗗櫒 //4銆佸姞杞芥枃妗B燚ocument聽娉ㄥ唽澶勭悊鍣� WebHandler handler=new WebHandler(); //5銆佽В鏋� parse.parse(Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/yn/server03/web.xml") ,handler); //将配置文件的信息转化为Map集合 wc=new WebContext(handler.getEntitys(), handler.getMappings()); } catch (Exception e) { e.printStackTrace(); System.out.println("解析配置文件错误"); } } /** * 通过url获取配置文件的servlet * @param url * @return */ public static Servlet getServletFromUrl(String url){ String clz = wc.getClz("/"+url); try { Class<?> className = Class.forName(clz); Servlet s = (Servlet) className.newInstance(); return s; } catch (Exception e) { e.printStackTrace(); return null; } }
public class WebHandler extends DefaultHandler{ private List<Entity> entitys ; private List<Mapping> mappings ; private Entity entity; private Mapping mapping; private boolean isMapping=false ; private String tag; //瀛樺偍鎿嶄綔鏍囩 @Override public void startDocument() throws SAXException { entitys =new ArrayList<Entity>(); mappings =new ArrayList<Mapping>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(null!=qName) { tag = qName; //瀛樺偍鏍囩鍚� if(tag.equals("servlet")) { entity = new Entity(); isMapping = false; }else if (tag.equals("servlet-mapping")) { mapping=new Mapping(); isMapping=true; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { String contents = new String(ch,start,length).trim(); if(null!=tag) { //处理空 if (isMapping) {//操作servlet-mapping if(tag.equals("servlet-name")) { mapping.setName(contents); }else if(tag.equals("url-pattern")) { if(contents.length()>0) { mapping.addPattern(contents); } } }else{ if(tag.equals("servlet-name")) { entity.setName(contents); }else if(tag.equals("servlet-class")) { if(contents.length()>0) { entity.setClz(contents); } } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName) { if(qName.equals("servlet")) { entitys.add(entity); }else if(qName.equals("servlet-mapping")) { mappings.add(mapping); } } tag = null; //tag丢弃了 } public List<Entity> getEntitys() { return entitys; } public List<Mapping> getMappings() { return mappings; }}
WebContext类
public class WebContext { private Map<String,String > entityMap=new HashMap<String,String>(); private Map<String,String> mappingMap=new HashMap<String,String>(); private List<Entity> entitys=null; private List<Mapping> mappings=null; public WebContext(List<Entity> entitys, List<Mapping> mappings) { this.entitys = entitys; this.mappings = mappings; //将entity集合转化为Map集合 for (Entity entity : entitys) { entityMap.put(entity.getName(), entity.getClz()); } //将mapping集合转化为Map集合 for (Mapping mapping : mappings) { for (String pattern : mapping.getPatterns()) { mappingMap.put(pattern, mapping.getName()); } } } /** * 通过url路径找到对应的class文件 * @param pattern * @return */ public String getClz(String pattern){ String name=mappingMap.get(pattern); return entityMap.get(name); }
Server02
//接受连接 public void receive(){ try { Socket client = ss.accept(); System.out.println("一个客户端建立了连接"); //获取请求协议 Request request=new Request(client); //获取响应协议 Response response=new Response(client); Servlet servlet = WebApp.getServletFromUrl(request.getUrl()); if (servlet!=null) { servlet.service(request, response); response.pushToBrowser(200); }else { //错误 response.pushToBrowser(404); } response.pushToBrowser(200); } catch (IOException e) { e.printStackTrace(); System.out.println("客户端连接出现错误"); } }
7.高效分发器.
使用多线程来处理每次请求都需启动服务器的问题
首先创建一个Dispatcher线程,在构造器中初始化客户端,请求和响应协议,如果异常,关闭客户端资源
在run方法中通过url获取的servlet方法获取对应的servlet,调用service方法,并在不同错误的时候推送对应的状态码,最后的方法是释放资源
package com.yn.server.core;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.Socket;import java.io.IOException;import java.nio.file.*;/** * 分发器:加入状态内容处理 404 505 及首页 * * @author yn * */public class Dispatcher implements Runnable { private Socket client; private Request request; private Response response ; public Dispatcher(Socket client) { this.client = client; try { //获取请求协议 //获取响应协议 request =new Request(client); response =new Response(client); } catch (IOException e) { e.printStackTrace(); this.release(); } } @Override public void run() { try { if(null== request.getUrl() || request.getUrl().equals("")) { InputStream is =Thread.currentThread().getContextClassLoader().getResourceAsStream("index.html"); byte b[] = new byte[1024]; int len = 0; int temp = 0; // 所有读取的内容都使用temp接收 while ((temp = is.read()) != -1) { // 当没有读取完时,继续读取 b[len] = (byte) temp; len++; } System.out.println(new String(b, 0, len)); response.print((new String(b,0,len))); response.pushToBrowser(200); is.close(); return ; } Servlet servlet= WebApp.getServletFromUrl(request.getUrl()); if(null!=servlet) { servlet.service(request, response); //关注了状态码 response.pushToBrowser(200); }else { //错误.... InputStream is =Thread.currentThread().getContextClassLoader().getResourceAsStream("error.html"); byte b[] = new byte[1024]; int len = 0; int temp = 0; // 所有读取的内容都使用temp接收 while ((temp = is.read()) != -1) { // 当没有读取完时,继续读取 b[len] = (byte) temp; len++; } response.print((new String(b,0,len))); int read = is.read(); response.pushToBrowser(404); is.close(); } }catch(Exception e) { try { response.println("你好我不好,我会马上好"); response.pushToBrowser(500); } catch (IOException e1) { e1.printStackTrace(); } } release(); } //释放资源 private void release() { try { client.close(); } catch (IOException e1) { e1.printStackTrace(); } }}
来源:https://www.cnblogs.com/yangnannnn/p/9961605.html