利用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="\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;
        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="\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 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();
            }
        }
    }
}

原文地址:https://www.cnblogs.com/DLKKILL/p/10368975.html

时间: 2024-11-03 14:46:22

利用Java手写简单的httpserver的相关文章

利用java servlet实现简单的web请求过滤和跳转

今日有两个微信web项目合并了,但是还有些链接指向废弃的项目,另外不想在服务器上运行两份相同web项目(1.影响性能.2.维护升级容易出错),因此决定写一个简单链接跳转的项目,spring的filter过滤器可以实现,但想想spring干这个有点大材小用,想到java的servlet可以支持通配符,因此用servlet写了一个简单的跳转程序,总共花了不到一小时的时间.废话少说上代码: 1 /** 2 * Servlet implementation class Default 3 */ 4 @W

利用java实现一个简单的远程监控程序

一般的远程监控软件都是用c或者c++等语言开发的,而使用java如何来实现相同的功能呢. 首先我们先介绍一下一个简单的远程监控程序的实现原理. 功能一,远程屏幕监视 (1) 必须要有监控端与被监控端,而且程序保持启动. (2) 被监控端获取本机的屏幕截屏发图给监控端. (3) 监控端在本地窗口中显示被监控端发送过来的图像. (4) (2)(3)步骤重复执行,这时在监控端即可实时监视到被监控端的桌面操作了. 功能二,远程控制 (1) 必须要有监控端与被监控端,而且程序保持启动. (2) 在监控端监

教你如何使用Java手写一个基于数组实现的队列

一.概述 队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表.在具体应用中通常用链表或者数组来实现.队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作.队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加. 在Java中队列又可以分为两个大类,一种是阻塞队列和非阻塞队列. 1.没有实现阻塞接口: 1)实现java.util.Queue的LinkList, 2)实现java.util.Abstra

利用java实现一个简单的链表结构

定义: 所谓链表就是指在某节点存储数据的过程中还要有一个属性用来指向下一个链表节点,这样的数据存储方式叫做链表 链表优缺点: 优点:易于存储和删除 缺点:查询起来较麻烦 下面我们用java来实现如下链表结构: 首先定义节点类: 复制代码package LinkTest;/** 链表节点类 @author admin */public class Node {private int value;//存储数据private Node next;//下一个节点/** 定义构造器 @param vlau

手写简单的jq雪花飘落

闲来无事,准备写个雪花飘落的效果,没有写太牛逼的特效,极大的简化了代码量,这样容易读取代码,用起来也很简单,对于那些小白简直是福利啊,简单易读易学.先直接上代码吧,然后再一一讲解,直接复制粘贴就可以拿来用了,改起来更是容易. <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>雪花飘落</title> </head> <style

手写简单PE

环境工具:Windows 10 010Editor 目标程序功能: 调用MessageBoxA弹出消息框. 1.构造DOS头 typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD

利用java多线程写的一个工具向MongoDb中存储大量数据

jdk:1.7mogodb:3.2mongodb_java_driver:3.2.2 1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.util.LinkedList; 7 import java.util.L

通过反射来手写简单的ORM SQlserver

不说废话,直接上干货,如发现问题,欢迎大家指出,谢谢! //------------------------------------MySQlServerORM [简单 CURD] using System; using System.Collections.Generic; using System.Linq; namespace COMMOM { using C10.ZRF.Model.Filter; using System.Configuration; using System.Data

手写简单的线程池

线程池的基本原理 声明任务队列.线程数量这两者数量主要由自己init,往队列中添加任务,如果超过数量则等待(阻塞),否则加入线程执行 import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; public cla