手写一个简易的HTTP服务器

手写HTTP服务器

首先给一个需求:用手写的http服务器实现登入、修改密码、校验用户是否登录和登出功能。
在手写http服务器之前,我们要知道http服务器是用来监听浏览器(客户端)发出的请求,然后在处理请求后将结果响应给浏览器的。
所以手写一个简易的http服务器要做的就是:
1、监听服务器请求
2、处理这个请求
3、将处理结果响应给浏览器


1、写出相关的HTML页面。

ajax-demo (给出登录界面)



    
        

登入

校验

使用Ajax,让浏览器向服务端发送一个路径为addUser2,body中包含userName和passWord(JSON格式)的POST请求。根据addUser2处理结束后所响应的值来判断账号密码是否正确。

如果正确则显示“密码账号正确”,并跳转到一个路径为ajax-demo3的页面。

如果错误则显示“密码账号错误”。

ajax-demo2 (给出修改密码的界面)



修改密码

修改密码

使用Ajax,让浏览器一旦访问这个页面,会自动的向服务端发送一个路径为addUser4的POST请求,根据addUser4处理结果的响应值来判断用户是否登入。

如果已经登入则显示“您已经登入”,点击提交按钮,浏览器将会向服务端发送一个路径为addUser3,body中包含passWord(为JSON格式)的POST请求。根据addUser3处理结束后所响应的值来判断密码是否重置成功。
如果重置失败会显示“没有相关账号”,并跳转至默认路径;重置成功则显示“密码重置成功”,并跳转至路径为ajax-demo3的页面。

如果没有登入,则会显示“您尚未登入”,并跳转至默认路径。

ajax-demo3 (给出跳转到修改密码的按钮和登出的按钮)



  
      
      Title
      

欢迎登入

使用Ajax,让浏览器一旦访问这个页面,会自动的向服务端发送一个路径为addUser4的POST请求,根据addUser4处理结果的响应值来判断用户是否登入。

如果已经登入则显示“您已经登入”,点击登出按钮,浏览器就会向服务端发送一个地址为addUser5的POST请求,并且跳转至路径为ajax-demo的页面;点击修改密码按钮则会跳转至路径为ajax-demo2的页面。

如果没有登入,则会显示“您尚未登入”,并跳转至默认路径。

2、手写http服务器来接收来自浏览器的请求,处理请求,将处理结果响应给浏览器

1、给出一个端口号

public class Server {
  public static void main(String[] args) throws IOException {
          new Server(12345);
      }
}

2、启动监听端口

public class Server {
 /*
    * 启动监听端口
    * */
    public Server(int port) throws IOException {
        if (port < 1 || port >65535){
            throw new DemoApplication("端口错误");
        }
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService pool = Executors.newFixedThreadPool(50);
        System.out.println("已经启动,开始监听端口"+port);
        while (true){
            Socket clientSocket = serverSocket.accept();
            if (clientSocket != null && !clientSocket.isClosed()){
                //首先服务端输出内容到客户端的输入流
                Runnable r = () -> {
                    try {
                        acceptToClient(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                };
                pool.submit(r);
            }
        }
    }
    public static void main(String[] args) throws IOException {
        new Server(12345);
    }
}

3、抛出未定义的异常

public class Server {
        //抛出异常
     private  static  class DemoApplication extends RuntimeException{
        public DemoApplication() {
            super();
        }

        public DemoApplication(String message) {
            super(message);
        }

        public DemoApplication(String message, Throwable cause) {
            super(message, cause);
        }

        public DemoApplication(Throwable cause) {
            super(cause);
        }

        protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
     /*
    * 启动监听端口
    * */
    
    public Server(int port) throws IOException {
        if (port < 1 || port >65535){
            throw new DemoApplication("端口错误");
        }
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService pool = Executors.newFixedThreadPool(50);
        System.out.println("已经启动,开始监听端口"+port);
        while (true){
            Socket clientSocket = serverSocket.accept();
            if (clientSocket != null && !clientSocket.isClosed()){
                //首先服务端输出内容到客户端的输入流
                Runnable r = () -> {
                    try {
                        acceptToClient(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                };
                pool.submit(r);
            }
        }
    }
    public static void main(String[] args) throws IOException {
        new Server(12345);
    }
}

4、封装响应报头(报文)

public class Server {
        //抛出异常
     private  static  class DemoApplication extends RuntimeException{
        public DemoApplication() {
            super();
        }

        public DemoApplication(String message) {
            super(message);
        }

        public DemoApplication(String message, Throwable cause) {
            super(message, cause);
        }

        public DemoApplication(Throwable cause) {
            super(cause);
        }

        protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
     /*
    * 启动监听端口
    * */
    
    public Server(int port) throws IOException {
        if (port < 1 || port >65535){
            throw new DemoApplication("端口错误");
        }
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService pool = Executors.newFixedThreadPool(50);
        System.out.println("已经启动,开始监听端口"+port);
        while (true){
            Socket clientSocket = serverSocket.accept();
            if (clientSocket != null && !clientSocket.isClosed()){
                //首先服务端输出内容到客户端的输入流
                Runnable r = () -> {
                    try {
                        acceptToClient(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                };
                pool.submit(r);
            }
        }
    }
    /*
    * 封装响应报头(报文)
    * */
    private void  writeToClient(OutputStream clientOut, String ContentType ,String cookieName,String cookieValue,String cookieTime, int responseCode, String responseDes, String content) throws IOException {
        clientOut.write(("HTTP/1.1 "+responseCode+ " " +responseDes+ "\r\n").getBytes());
        clientOut.write(("Date: " + (new Date()).toString() + "\r\n").getBytes());
        clientOut.write(("Content-Type: "+ ContentType +"; charset=UTF-8\r\n").getBytes());
        clientOut.write(("Set-Cookie: "+cookieName+"="+cookieValue+";"+"max-age="+cookieTime+"\r\n").getBytes(StandardCharsets.UTF_8));
        clientOut.write("\r\n".getBytes());//空行
        clientOut.write(content.getBytes());
        clientOut.flush();
        clientOut.close();
    }

    public static void main(String[] args) throws IOException {
        new Server(12345);
    }
}

5、处理请求
如何让浏览器记住我已经登入了呢?HTTP是一个无状态协议,即自身不对请求和响应之间的通讯状态进行保存。这里就得引用Cookie技术来实现保存了。

我们在网页的输入框中输入用户名点击登入按钮,浏览器就会向服务器提交一个POST请求(数据存放在body中)。服务端监听到POST请求后,通过响应的逻辑处理获取浏览器提交的用户名。服务端存储这个cookie,再通过Set-Cookie 响应报头,告诉浏览器来储存这个(携带用户名的)cookie。从此,浏览器的每一次请求(在cookie的生命周期中)都会携带这cookie进行访问。所以只要对浏览器是否携带(存储用户名的)cookie进行判断就能得知用户是否登入。

登出也是这个道理。点击登出按钮后,浏览器向服务端发送一个POST请求。服务端获取到这个请求后开始相应的逻辑判断,将cookie中的max-age(cookie的生命周期)设置成0,就会让这个cookie过期,再通过Set-Cookie 响应报头告诉浏览器这个cookie过期了(发送新的cookie)。自此浏览器在接下来的请求中就不会携带这个cookie了。完成了登出操作。

其他的操作比如:如何设置默认路径、如何读取请求、如何获取请求报文的URL等等的逻辑判断,我已经在代码中加了注释来解析。

public class Server {
    /*
    * 抛出异常
    * */
    private  static  class DemoApplication extends RuntimeException{
        public DemoApplication() {
            super();
        }

        public DemoApplication(String message) {
            super(message);
        }

        public DemoApplication(String message, Throwable cause) {
            super(message, cause);
        }

        public DemoApplication(Throwable cause) {
            super(cause);
        }

        protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    /*
    * 启动监听端口
    * */
    public Server(int port) throws IOException {
        if (port < 1 || port >65535){
            throw new DemoApplication("端口错误");
        }
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService pool = Executors.newFixedThreadPool(50);
        System.out.println("已经启动,开始监听端口"+port);
        while (true){
            Socket clientSocket = serverSocket.accept();
            if (clientSocket != null && !clientSocket.isClosed()){
                //首先服务端输出内容到客户端的输入流
                Runnable r = () -> {
                    try {
                        acceptToClient(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                };
                pool.submit(r);
            }
        }
    }
    private void acceptToClient(Socket clientSocket) throws IOException {
        //使用 InputStream 来读取浏览器的请求报文
        InputStream clientIn = clientSocket.getInputStream();
        //使用 BufferedReader 将InputStream强转成 BufferedReader。(InputStream读取到的是字节,BufferedReader读取到的是字符,我们需要的是字符,所以强转成BufferedReader)
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientIn,"UTF-8"));
        OutputStream clientOut = clientSocket.getOutputStream();
        String content = "";
        String cookieName = "";
        String cookieValue = "";
        String cookieTime = "";
        //如果获取到的有效长度为0
        if (clientIn.available()==0){
            writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK","

OK

"); return; } /* * 读取第一行要进行特殊处理,从中获取到请求报文中的 方法 和 URL * */ String firstLine = bufferedReader.readLine(); System.out.println("读取到的第一行是"+firstLine); String requestUri = firstLine.split(" ")[1]; System.out.println("读取数组中的URL"+requestUri); String method = firstLine.split(" ")[0]; System.out.println("这次请求的方法是"+method); String str = ""; String result = ""; /* * 判断路径是否是addUser2,如果是则进入这个处理逻辑。 * */ if(requestUri.equals("/addUser2")){ System.out.println("进入addUser2"); if(method.equals("POST")){ String d = ""; int e = 0; //将读取到的请求报文按行读取 while ((str=bufferedReader.readLine())!= null){ d = str.split(" ")[0]; //将Content-Length中的长度提取出来 if(d.equals("Content-Length:")){ e = Integer.parseInt(str.split(" ")[1]); System.out.println("e"+e); } System.out.println(str); //读取到空行说明请求报文中首部字段已经读取完毕(请求首部字段和内容实体中间隔了一个空行),即将进入内容实体(body) if ((str = bufferedReader.readLine()).equals("")){ System.out.println("读取到空行"); break; } } char[] f = new char[e]; int g = 0; int h = 0; int j = e; //read(暂存区,偏移量,读取的最大长度) 是读取字符放入f缓冲区 /*使用read方法而不是readLine是应为readLean在这里会出现堵塞,会一直卡在这里。 所以使用read方法将剩余的body长度(Content-Length中的长度)读取出来*/ while ((g = bufferedReader.read(f,h,e))!=-1){ h = h +g; System.out.println(h); break; } //将f暂存区的内容一一输出 for(int i = 0 ; i < e ; i++){ result += f[i]; } System.out.println("读取到的body是"+result); } System.out.println("进入addUser2逻辑处理"); //获取body中的JSON数据 ObjectMapper mapper = new ObjectMapper(); People people = mapper.readValue(result,People.class); System.out.println(people.userName); System.out.println(people.passWord); String userName = people.userName; String passWord = people.passWord; //查询数据库 //2.获取SqlSession对象 Sql sql = new Sql(); SqlSession sqlSession = sql.Sql().openSession(); //3.获取Mapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //4.执行方法,将数据库中的账号密码与获取到的账号密码进行比对 User users =userMapper.selectAll(userName, passWord); System.out.println(users); //释放资源 sqlSession.close(); //即将响应给浏览器的处理结果 if (users != null) { //将用户名密码通过Set-Cookie的报头告诉浏览器储存这个cookie值 cookieName = "userName"; cookieValue = userName; content = "true"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); }else { content ="false"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } } /* * 判断是否是addUser3,如果是则进入 * */ System.out.println(cookieName); System.out.println(cookieValue); if(requestUri.equals("/addUser3")){ System.out.println("进入addUser3方法"); String k = ""; String d = ""; String l = ""; int e = 0; if(method.equals("POST")){ while ((str=bufferedReader.readLine())!= null){ d = str.split(" ")[0]; //获取Cookie中的值,用于后期判断 if (d.equals("Cookie:")) { k = str.split(" ")[2]; System.out.println("k" + k); } if(d.equals("Content-Length:")){ e = Integer.parseInt(str.split(" ")[1]); System.out.println("e"+e); } System.out.println(str); if ((str = bufferedReader.readLine()).equals("")){ System.out.println("读取到空行"); break; } } char[] f = new char[e]; int g = 0; int h = 0; int j = e; //read 是读取字符放入f缓冲区 while ((g = bufferedReader.read(f,h,e))!=-1){ h = h +g; System.out.println(h); break; } for(int i = 0 ; i < e ; i++){ result += f[i]; } System.out.println("读取到的body是"+result); } l = k.split("=")[0]; k = k.split("=")[1]; System.out.println("l"+l); System.out.println("k"+k); //将获取到的cookie值进行比对,如果一致则进入逻辑 if ("userName".equals(l)) { System.out.println("成功进入"); String value = k; System.out.println(l + ":" + value); //提取JSON中的值 ObjectMapper mapper = new ObjectMapper(); People people = mapper.readValue(result,People.class); String userName = value; String passWord = people.passWord; System.out.println("passWord"+passWord); //修改密码 User user = new User(); user.setUserName(userName); user.setPassWord(passWord); //2.获取SqlSession对象 Sql sql = new Sql(); SqlSession sqlSession = sql.Sql().openSession(); //3.获取Mapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //4.执行方法,将数据库中的值进行更改 int update = userMapper.update(user); System.out.println(update); //提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); System.out.println(result); //即将响应给浏览器的内容 if (update !=0) { content = "true"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } else { content = "false"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } } } /* * 判断是否是addUser4,如果是则进入 * */ if(requestUri.equals("/addUser4")){ System.out.println("进入addUser4方法"); String d = ""; String e = ""; if(method.equals("POST")) { while ((str = bufferedReader.readLine()) != null) { d = str.split(" ")[0]; if (d.equals("Cookie:")) { e = str.split(" ")[2]; System.out.println("e" + e); break; } } } //从请求首部字段中获取cookie值。 d = e.split("=")[0]; //e = e.split("=")[1]; System.out.println("cookieName="+d); //如果浏览器中已经存放了这个cookie值 if ("userName".equals(d)) { content = "true"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); }else { content = "false"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } } /* * 判断是否是addUser5,如果是则进入 * */ if(requestUri.equals("/addUser5")){ System.out.println("进入addUser5"); String d = ""; String e = ""; if(method.equals("POST")) { while ((str = bufferedReader.readLine()) != null) { d = str.split(" ")[0]; if (d.equals("Cookie:")) { e = str.split(" ")[2]; System.out.println("e" + e); break; } } } d = e.split("=")[0]; e = e.split("=")[1]; System.out.println("cookieName="+d); System.out.println("cookieValue="+e); cookieName = d; cookieValue = e; //将cookie中的生命周期(Max-age)设置成0,让这个cookie值过期。 cookieTime ="0"; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } if(requestUri.equals("/favicon.ico")){ writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK","favicon.ico"); return; } System.out.println("=========================================>>>>>>>>>>>"); //判断是否带路径搜索,没带路径的默认搜索ajax-demo.html String resourcePath = requestUri.equals("/") ? "ajax-demo" : requestUri.substring(1); System.out.println(resourcePath); //打印获取的html String a = ""; String htmlStr = ""; try { BufferedReader in = new BufferedReader(new FileReader("src/webapp/"+resourcePath +".html")); while ((a = in.readLine()) != null) { htmlStr =htmlStr + "\n" +a; } } catch (IOException e) { System.out.print("错误"); } //读取资源的内容 System.out.println(htmlStr); //找不到资源直接返回404 if (htmlStr == null){ writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,404,"Not Found","

404 FILE NOT FOUND

"); return; } content = htmlStr; writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content); } /* * 封装响应报头(报文) * */ private void writeToClient(OutputStream clientOut, String ContentType ,String cookieName,String cookieValue,String cookieTime, int responseCode, String responseDes, String content) throws IOException { clientOut.write(("HTTP/1.1 "+responseCode+ " " +responseDes+ "\r\n").getBytes()); clientOut.write(("Date: " + (new Date()).toString() + "\r\n").getBytes()); clientOut.write(("Content-Type: "+ ContentType +"; charset=UTF-8\r\n").getBytes()); clientOut.write(("Set-Cookie: "+cookieName+"="+cookieValue+";"+"max-age="+cookieTime+"\r\n").getBytes(StandardCharsets.UTF_8)); clientOut.write("\r\n".getBytes());//空行 clientOut.write(content.getBytes()); clientOut.flush(); clientOut.close(); } public static void main(String[] args) throws IOException { new Server(12345); } }

3、查收,是否符合预期

自此,这个手写的http服务器就已经完成了,来看看效果如何。
首先直接访问路径为ajax-demo2和ajax-demo3会发生什么。
手写一个简易的HTTP服务器_第1张图片手写一个简易的HTTP服务器_第2张图片
符合预期。
登入后呢?
手写一个简易的HTTP服务器_第3张图片
手写一个简易的HTTP服务器_第4张图片
手写一个简易的HTTP服务器_第5张图片
可以看到,此时请求中Cookie报头已经携带了userName的信息,这时在路径为ajax-demo2的页面点击登出会发生什么。
手写一个简易的HTTP服务器_第6张图片
可以看到响应报文中的Set-Cookie中的max-age被设置成了0,让cookie(username)失效。
手写一个简易的HTTP服务器_第7张图片
可以看到,自此浏览器的请求报文中已经不再携带username这个cookie。

符合预期,结束!

你可能感兴趣的:(httpajax)