• 利用Java手写简单的httpserver


    前言:

    在看完尚学堂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="
    ";
    	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;
    		else
    			return 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="
    ";
    	public static final String BANK=" ";
    	
    	private StringBuilder headerinfo;
    	private StringBuilder content;
    	private BufferedWriter wr;
    	private int len;
    	
    	public Response() {
    		// TODO Auto-generated constructor stub
    		headerinfo=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;
    		}
    	}
    	
    	@Override
    	public void run() {
    		System.out.println(req.getUrl()+"   ***");
    		Servlet servlet=Webapp.getServlet(req.getUrl());
    		if(servlet!=null)
    			servlet.service(req, rep);
    		else
    			code=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 {
    
    	
    	@Override
    	public 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>");
    	}
    
    	@Override
    	public 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;
    		}
    	@Override
    	public void startDocument() throws SAXException {
    		entityList=new ArrayList<Entity>();
    		mappingList=new ArrayList<Mapping>();
    	}
    
    	@Override
    	public 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);
    //		}
    	}
    
    	@Override
    	public 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;
    	}
    
    	@Override
    	public 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;
    	}
    
    	@Override
    	public 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 exception
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    
  • 相关阅读:
    python 循环的概念
    python 字典的基本操作
    短路表达式
    快捷键myeclipse
    nginx静态文件访问
    安装mysql
    安装mongodb
    安装tomcat
    安装jdk8
    安装node和pm2
  • 原文地址:https://www.cnblogs.com/DLKKILL/p/10368975.html
Copyright © 2020-2023  润新知