前言:

在看完尚学堂JAVA300中讲解如何实现一个最简单的httpserver部分的视频之后,

一、前置知识

1.HTTP协议

  当前互联网网页访问主要采用了B/S的模式,既一个浏览器,一个服务器,浏览器向服务器请求资源,服务器回应请求,浏览器再将接收到的回应解析出来展现给用户。这一问一答的过程可以抽象成浏览器向服务器发送一个Request然后服务器返回一个Response的过程
  其中Request和Reponse在HTTP中有有具体的格式要求

  • 一个Request的例子
Method Path-to-resource Http/Version-number
User-agent 浏览器的类型Accept-charset 用户首选的编码字符集……
Accept 浏览器接受的MIME类型
Accept language 用户选择的接受语言
Accept-charset 用户首选的编码字符集
空行
Option Request Body

如表格所示,第一行首先是请求方式,后跟请求资源路径(url)如果请求方式是GET,则请求参数跟在请求路径里,以?分开,然后一个空格,后跟HTTP版本。后几行为固定格式内容。如果请求方式为POST,则隔一个空行后,跟的请求体的内容,里面有请求参数。

  • 一个Response内容
Http/Version-number Statuscode message
Server 服务器的类型信息
Content-type 响应的MIME类型信息
Content-length 被包含在相应类型中的字符数量
空行
Option Response Body

和Request类似,同样包含响应头和响应体两部分。第一行的Statuscode标识了状态参数,404表示请求资源没有找到,500表示服务器错误,200表示成功。响应体里面包含的是响应内容

该部分具体可以参考博文:

2.JAVA网络编程

在Java中提供了两种网络传输方式的实现,面向数据的UDP传输方式和面向连接的TCP传输方式,这里选用TCP方式。
在TCP方式中,服务端的编写主要依靠类Socket和类ServerSocket,通过这两个类可以建立一个TCP连接。

具体方法是:

  1. 首先新建一个ServerSocket对象server,指明端口号信息
  2. 然后使用server.accept()函数监听端口,监听到连接以后返回一个Socket对象
  3. 通过这个Socket对象,以及里面的输入流和输出流,我们就可以获得传过来的信息以及返回信息

二、具体实现

1.服务器类

首先我们需要建立一个服务器类,负责不断监听端口,获得Socket,然后再利用获得Socket新建一个分发器(Dispatcher),该分发器支持多线程,所以一个分发器专门负责处理一个连接,而服务器只负责不断接收连接,建立分发器。
具体代码如下:

package top.dlkkill.httpserver;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Server {private boolean flag=false;private ServerSocket server;public static void main(String[] args) throws IOException {Server myserver=new Server(8888);myserver.start();}public Server(int port) {try {server=new ServerSocket(port);} catch (IOException e) {this.stop();}}public void start() throws IOException {this.flag=true;this.revice(server);}public void revice(ServerSocket server) throws IOException {while(flag) {Socket client=server.accept();new Thread(new Dispatcher(client)).start();}}public void stop() {flag=false;}
}

2.封装Request和Response

为了方便解析Request和返回Response,我们需要抽象出两个对象(Request类和Response对象)。

首先封装Request

  Request对象的作用是解析请求信息,将请求方式,请求资源路径,请求参数解析分离出来,构建一个Request对象我们需要传入一个参数------>输入流。这样我们就可以从输入流中读入请求信息然后开始解析。

package top.dlkkill.httpserver;import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;public class Request {private String url;private String method;private String info;private Map<String,List<String>> paramterMapValues;private InputStream is;public static final String CRLF="\r\n";public static final String BANK=" ";private Request() {url=null;method=null;is=null;info=null;paramterMapValues=new HashMap<String,List<String>>();}public Request(InputStream is) {this();this.is=is;try {create();} catch (IOException e) {this.is=null;System.out.println("Request 创建错误");return;}}public String getMethod() {return this.method;}public String getUrl() {return url;}private void create() throws IOException{getInfo();getUrlAndParamter();}/*** 根据页面name获取对应所有值* @return String[]*/public String[] getparamterValues(String name) {List<String> paramterValues=null;if((paramterValues=paramterMapValues.get(name))==null) {return null;}else {return paramterValues.toArray(new String[0]);}}/*** 根据页面name获取单个值* @return String[]*/public String getparamterValue(String name) {String values[]=getparamterValues(name);if(values==null)return null;elsereturn values[0];}/*** 得到请求信息* @throws IOException*/private void getInfo() throws IOException {byte bytes[]=new byte[20480];int len=is.read(bytes);info=new String(bytes,0,len);}/*** 处理得到url资源请求路径和请求参数值* @return*/private void getUrlAndParamter(){String firstline=info.substring(0,info.indexOf(CRLF));//System.out.println("FirstLine:  "+firstline);String paramter="";this.method=firstline.substring(0,firstline.indexOf("/")).trim();String tempurl=firstline.substring(firstline.indexOf("/"),firstline.indexOf("HTTP/")).trim();System.out.println("tempurl:  "+tempurl);if(this.method.equalsIgnoreCase("post")) {this.url=tempurl;paramter=info.substring(info.lastIndexOf(CRLF)).trim();}else {if(tempurl.contains("?")) {//split函数里面的参数实际上需要的是正则表达式,普通字符串还好,?号是特殊字符String[] urlarry=tempurl.split("\\?");this.url=urlarry[0];paramter=urlarry[1];}else {this.url=tempurl;}}//解析参数parseParmter(paramter);return;//System.out.println(this.url);//System.out.println(paramter);}/*** 解析请求参数,转换成键值对形式* @param str*/private void parseParmter(String str) {if(str==null||str.equals("")||str.trim().equals(""))return;StringTokenizer st=new StringTokenizer(str,"&");while(st.hasMoreTokens()) {String temp=st.nextToken();String[] KeyAndValues=temp.split("=");if(KeyAndValues.length==1) {KeyAndValues=Arrays.copyOf(KeyAndValues,2);KeyAndValues[1]=null;}String key=KeyAndValues[0].trim();String value=KeyAndValues[1]==null?null:KeyAndValues[1].trim();if(!paramterMapValues.containsKey(KeyAndValues[0])){paramterMapValues.put(key,new ArrayList<String>());}paramterMapValues.get(key).add(decode(value, "gbk"));}}/*** 解决中文编码问题* @param value* @param code* @return*/private String decode(String value,String code) {try {return java.net.URLDecoder.decode(value, code);} catch (UnsupportedEncodingException e) {}return null;}
}

然后我们进行封装Response.
  Response主要分为两部分(响应头和响应体)
  构建Response需要传入通过Socket获得的输出流,利用这个输出流我们才可以写回信息
响应头格式较为固定,所有我们在Response中应该有一个私有方法根据响应码自动构建响应头信息。
  然后我们还需要一个共有方法void println(String msg),其他类利用该方法写入对应的响应体部分。
最后需要有一个send()方法,自动整合响应体和响应体内容,并将所有内容发送出去。

package top.dlkkill.httpserver;import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;public class Response {public static final String CRLF="\r\n";public static final String BANK=" ";private StringBuilder headerinfo;private StringBuilder content;private BufferedWriter wr;private int len;public Response() {// TODO Auto-generated constructor stubheaderinfo=new StringBuilder();content=new StringBuilder();if(content==null)System.out.println("error");len=0;}public Response(OutputStream os){this();wr=new BufferedWriter(new OutputStreamWriter(os));}public void createHeaderinfo(int code) {System.out.println("code is "+code);headerinfo.append("HTTP/1.1").append(BANK);switch (code) {case 200:headerinfo.append(code).append(BANK).append("OK");break;case 404:headerinfo.append(code).append(BANK).append("404 not found");break;case 500:headerinfo.append(code).append(BANK).append("error");break;default:headerinfo.append(code).append(BANK).append("error");break;}headerinfo.append(CRLF);headerinfo.append("Server:dlkkill server/0.1").append(CRLF);headerinfo.append("Date:").append(new Date()).append(CRLF);headerinfo.append("Content-type:text/html;charset=GBK").append(CRLF);//正文长度,字节长度headerinfo.append("Content-Length:").append(len).append(CRLF);//空行分隔符headerinfo.append(CRLF);//System.out.println(headerinfo.toString());}public Response println(String msg) {//System.out.println(msg);if(content==null)System.out.println(msg);content.append(msg);len+=msg.getBytes().length;return this;}public void pushToClient(int code) throws IOException {if(wr==null) {code=500;}createHeaderinfo(code);wr.write(headerinfo.toString());wr.write(content.toString());wr.flush();}
}

3.创建分发器

  我们需要有一个类专门一对一处理一个连接,并且该类要支持多线程。所以我们抽象出来一个分发器类。该类负责专门一对一处理一个连接
  该类拥有一个私有属性Socket client。利用该属性,该类可以创建一个Request和一个Response,然后该类再根据请求的url,利用Webapp类(该类用于生成处理不同请求的不同的类)获得对应的类,启动该类进行处理。
最后该类再调用Response提供的pushToClient方法将所有信息推送给浏览器,然后关闭连接。

具体代码如下:

package top.dlkkill.httpserver;import java.io.IOException;
import java.net.Socket;public class Dispatcher implements Runnable {private Socket client;private Request req;private Response rep;private int code=200;public Dispatcher(Socket client) {this.client=client;try {req=new Request(client.getInputStream());rep=new Response(client.getOutputStream());} catch (IOException e) {code=500;}}@Overridepublic void run() {System.out.println(req.getUrl()+"   ***");Servlet servlet=Webapp.getServlet(req.getUrl());if(servlet!=null)servlet.service(req, rep);elsecode=404;try {rep.pushToClient(code);} catch (IOException e) {code=500;}try {rep.pushToClient(code);} catch (IOException e) {}CloseUtil.closeAll(client);}
}

4.抽象处理类Servlet

首先我们将该处理类抽象成一个abstract Servlet类,该类负责根据不同的请求进行处理
该抽象类提供多个抽象方法doGet、doPost方法等分别处理不同的请求,传入参数为(Request,Response)这两个参数,在该方法内进行处理。
提供一个service方法根据不同的请求调用不同的方法
具体代码:

package top.dlkkill.httpserver;import java.net.Socket;public abstract class Servlet {public Servlet() {}public void service(Request req,Response rep) {if(req.getMethod().equalsIgnoreCase("get")) {this.doGet(req, rep);}else {this.doPost(req, rep);}}public abstract void doGet(Request req,Response rep);public abstract void doPost(Request req,Response rep);
}

一个实例:

package top.dlkkill.httpserver;public class loginServlet extends Servlet {@Overridepublic void doGet(Request req, Response rep) {rep.println("<head>" + "    <title>test</title>" + "</head>" + "<body>" + "<p>hellow</p>"+"<form action=\"http://localhost:8888/index\" method=\"POST\">" + "name: <input type=\"text\" name=\"name\">" + "password: <input type=\"password\" name=\"pwd\">" + "<input type=\"submit\" value=\"submit\">" + "</form>" + "</body>");}@Overridepublic void doPost(Request req, Response rep) {this.doGet(req, rep);}}

5.处理类生成工厂

为了编程的灵活性,我们将该httpserver写出可以根据一个xml配置文件知道有多少种分别处理什么url请求的类,该xml就负责记录这种映射关系
首先需要一个ServletContext类,该类有两个属性private Map<String,String> servlet和private Map<String,String> map,分别用来记录名称到类存储地址之间的映射和url到名称之间的映射

package top.dlkkill.httpserver;import java.util.HashMap;
import java.util.Map;public class ServletContext {//名称到类存储地址之间的映射private Map<String,String> servlet;//url到名称之间的映射private Map<String,String> map;public void setServlet(Map<String, String> servlet) {this.servlet = servlet;}public void setMap(Map<String, String> map) {this.map = map;}public ServletContext() {servlet=new HashMap<String, String>();map=new HashMap<String, String>();}public Map<String, String> getServlet() {return servlet;}public Map<String, String> getMap() {return map;}}

然后需要一个Webapp类
该类负责读入xml文件并且进行解析,根据xml文件配置的内容,为分发器生成不同的servlet处理类。
生成不同的类利用的Java的类加载机制,可以在代码中获取class信息然后new一个类出来
解析xml文件我们使用的是SAXParser解析器,为了利用该解析器,我们还需要实现一个继承于DefaultHandler的类

实现代码:

package top.dlkkill.httpserver;import java.io.IOException;
import java.util.List;
import java.util.Map;import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;import org.xml.sax.SAXException;public class Webapp {private static ServletContext servletcontext;static{servletcontext=new ServletContext();Map<String,String> servlet=servletcontext.getServlet();Map<String,String> map=servletcontext.getMap();
//      servlet.put("index", "top.dlkkill.httpserver.indexServlet");
//      servlet.put("login", "top.dlkkill.httpserver.loginServlet");
//      map.put("/login", "login");
//      map.put("/index", "index");SAXParserFactory parserfactor=SAXParserFactory.newInstance();WebHandler hd=new WebHandler();SAXParser parser;try {parser=parserfactor.newSAXParser();if(null==Thread.currentThread().getContextClassLoader().getResourceAsStream("top/dlkkill/httpserver/web.xml"))System.out.println("error");parser.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("top/dlkkill/httpserver/web.xml"), hd);List<Entity> entityList=hd.getEntityList();List<Mapping> mappingList=hd.getMappingList();for (Mapping mapping : mappingList) {String name=mapping.getName();List<String> urlList=mapping.getUrl();for (String url:urlList) {map.put(url, name);}}for (Entity entity:entityList) {String servletname=entity.getName();String clz=entity.getClz();servlet.put(servletname, clz);}} catch (ParserConfigurationException | SAXException |IOException e) {}}public static Servlet getServlet(String url) {Map<String,String> servlet=servletcontext.getServlet();Map<String,String> map=servletcontext.getMap();String className=servlet.get(map.get(url));Servlet temp=null;Class<?> clz=null;try {System.out.println("classname:"+className);if(className!=null)clz=Class.forName(className);} catch (ClassNotFoundException e) {return null;}try {if(clz!=null)temp=(Servlet)clz.newInstance();} catch (InstantiationException e) {return null;} catch (IllegalAccessException e) {return null;}return temp;}
}
package top.dlkkill.httpserver;import java.util.ArrayList;
import java.util.List;import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;public class WebHandler extends DefaultHandler {private List<Entity> entityList;private List<Mapping> mappingList;private Entity entity;private Mapping mapping;private String tag;private boolean isMap;public WebHandler() {}public List<Entity> getEntityList() {return entityList;}public List<Mapping> getMappingList() {return mappingList;}@Overridepublic void startDocument() throws SAXException {entityList=new ArrayList<Entity>();mappingList=new ArrayList<Mapping>();}@Overridepublic void endDocument() throws SAXException {
//      for (Mapping mapping : mappingList) {
//          if(mapping==null)
//              continue;
//          String name;
//          if(mapping.getName()!=null)
//              name=mapping.getName();
//          else
//              name="null";
//          List<String> urlList=mapping.getUrl();
//          for (String url:urlList) {
//              System.out.println(name+"---->"+url);
//          }
//      }
//      for (Entity entity:entityList) {
//          String servletname=entity.getName();
//          String clz=entity.getClz();
//          System.out.println(servletname+"---->"+clz);
//      }}@Overridepublic void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {//System.out.println("开始处理"+"--->"+qName);if(null!=qName) {if(qName.equals("servlet")) {isMap=false;entity=new Entity();}else if(qName.equals("servlet-mapping")){isMap=true;mapping=new Mapping();}}tag=qName;}@Overridepublic void endElement(String uri, String localName, String qName) throws SAXException {//System.out.println("结束处理"+"--->"+qName);if(null!=qName) {if(qName.equals("servlet")) {entityList.add(entity);}else if(qName.equals("servlet-mapping")){mappingList.add(mapping);}}tag=null;}@Overridepublic void characters(char[] ch, int start, int length) throws SAXException {String str=new String(ch, start, length);//System.out.println("处理中"+"--->"+str);if(tag!=null&&str!=null&&!str.trim().equals("")) {if(!isMap) {if(tag.equals("servlet-name"))entity.setName(str);else if(tag.equals("servlet-class"))entity.setClz(str);}else {if(tag.equals("servlet-name"))mapping.setName(str);else if(tag.equals("url"))mapping.getUrl().add(str);}}}}

6.一个工具类

该类负责关闭连接,连接关闭了那与该连接有关的流也就关闭了

package top.dlkkill.httpserver;import java.io.Closeable;public class CloseUtil {public static void closeAll(Closeable ...io) {for (Closeable closeable : io) {try {if(closeable!=null)closeable.close();}catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}}
}

转载于:https://www.cnblogs.com/DLKKILL/p/10368975.html

利用Java手写简单的httpserver相关推荐

  1. 高级JAVA - 手写简单的重试组件学习Spring Retry

    目录 一 . 定义注解 二 . 利用cglib代理扩展重试业务 三 . 编写代理类 , 使用自定义的XRetryInterceptor作为拦截器 四 . 编写相关业务方法 , 测试代码 五 . 测试结 ...

  2. 手写简单的HttpServer基于Java nio 实现socket异步通信(请求映射注解方式)

    HttpServer服务类 1 package javax.servlet.http.server2; 2 3 import java.io.IOException; 4 import java.ne ...

  3. Java 手写一个SQL分页

    Java手写一个类似PageHelper的分页SQL 目前分页插件众所周知的莫过于和mybatis完美融合的PageHelper了,简单两行代码就实现了sql分页,配合PageInfo类,将数据总数量 ...

  4. DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Functional)利用MNIST(手写数字图片识别)数据集实现多分类预测

    DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Functional)利用MNIST(手写数字图片识别)数据集实现多分类预测 目录 输出结果 设计思路 核心代码 输出结果 下边两张 ...

  5. DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Sequential)利用MNIST(手写数字图片识别)数据集实现多分类预测

    DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Sequential)利用MNIST(手写数字图片识别)数据集实现多分类预测 目录 输出结果 设计思路 核心代码 输出结果 1.10 ...

  6. java 手写 jvm高性能缓存

    java 手写 jvm高性能缓存,键值对存储,队列存储,存储超时设置 缓存接口 1 package com.ws.commons.cache; 2 3 import java.util.functio ...

  7. java完成一个学生信息调查程序_利用Java设计一个简单的学生信息管理程序

    利用Java设计一个简单的控制台学生信息管理程序 此程序可作为课设的参考,其中信息存储于文件中. 创建了学生类Student,用于存储学号等的信息.创建StudentFunction类,用于实现诸如学 ...

  8. 手写简单的HashMap(jdk1.7)

    手写简单的HashMap(jdk1.7) public class MyHashMap<K, V> { //创建一个节点数组 private Entry1<K, V>[] ta ...

  9. 使用代理模式手写简单的数据库连接池

    使用代理模式手写简单的数据库连接池 JDBC直连数据库 思考 改造 ConnectionProxy ConnectionPool ProxyMain 运行结果 代理模式 与装饰器的区别 JDBC直连数 ...

  10. Dubbo——手写简单版Dubbo框架

    摘要 本博文将详细的介绍的一个手写简单版本的dubbo的实现. 项目架构原理图 项目源码实现 https://github.com/2462612540/DubboPrinciple/tree/mas ...

最新文章

  1. 文件列表出现分页按钮
  2. XSS中JavaScript加密以及Filter bypass
  3. git常用命令(克隆、分支、tag标签、推送、日志查看,常用命令总结)、将本地代码第一次提交到远程git仓库、过滤掉不提交的内容
  4. java黑色_java-透明的黑色圆圈
  5. I/O多路转接之select
  6. resset-rem.css
  7. “中国锦鲤” 信小呆辞职旅行,这辈子真的不用工作了?
  8. 【题解】(图论) —— POJ 0719:拓扑排序
  9. 第5讲 zend原理深度剖析
  10. Confluence OGNL表达式注入漏洞复现与分析(CVE-2022-26134)
  11. Hibernate pojo对象的三种状态
  12. 麦克风里有电流声,杂音怎么办
  13. 关于applet小程序在浏览器上运行的备注
  14. Proteus STM32仿真学习板
  15. 阿里大数据之路:数据管理篇大总结
  16. java递归把list菜单列表转为菜单树
  17. 2、selenium原理和启动浏览器(Chromoption类)
  18. 理解分布式一致性:Paxos协议之Multi-Paxos
  19. iframe自适应高度解决方案
  20. android权限 启动失败怎么办,Android上的异常“打开失败:EACCES(权限被拒绝)”

热门文章

  1. 负载均衡实现 --- LVS的介绍、调度算法、NAT模式的搭建
  2. [bug]使用SharpZipLib遇到中文名称乱码问题
  3. http协议-响应和请求
  4. C程序设计语言(2)文摘
  5. IronPython for ASP.NET 部署注意事项
  6. 重磅:阿里发布神器工具,直接帮你改代码,我高潮了!网友:工作量又减轻了!...
  7. count(1)、count(*) 与 count(列名) 的执行区别
  8. Postman都脱单了,我决定用他女朋友Postwoman了
  9. 李彦宏笑称跟马云同场压力大,马云:压根不知你在场
  10. 全面了解Nginx主要应用场景