【愚公系列】2022年01月 Java教学课程 71-自建HTTP服务器解析浏览器请求案例

举报
愚公搬代码 发表于 2022/01/15 23:44:24 2022/01/15
【摘要】 一.自建HTTP服务器解析浏览器请求案例 1.环境搭建实现步骤编写HttpServer类,实现可以接收浏览器发出的请求其中获取连接的代码可以单独抽取到一个类中代码实现// 服务端代码public class HttpServer { public static void main(String[] args) throws IOException { //1.打开服务端...

一.自建HTTP服务器解析浏览器请求案例

1.环境搭建

  • 实现步骤

    • 编写HttpServer类,实现可以接收浏览器发出的请求
    • 其中获取连接的代码可以单独抽取到一个类中
  • 代码实现

    // 服务端代码
    public class HttpServer {
        public static void main(String[] args) throws IOException {
            //1.打开服务端通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //2.让这个通道绑定一个端口
            serverSocketChannel.bind(new InetSocketAddress(10000));
            //3.设置通道为非阻塞
            serverSocketChannel.configureBlocking(false);
            //4.打开一个选择器
            Selector selector = Selector.open();
    
            //5.绑定选择器和服务端通道
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    
            while(true){
                //6.选择器会监视通道的状态.
                int count = selector.select();
                if(count != 0){
                    //7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
                    //获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while(iterator.hasNext()){
                        //selectionKey 依次表示每一个服务端通道的令牌
                        SelectionKey selectionKey = iterator.next();
                        if(selectionKey.isAcceptable()){
                            //获取连接
                            AcceptHandler acceptHandler = new AcceptHandler();
                            acceptHandler.connSocketChannel(selectionKey);
                        }else if(selectionKey.isReadable()){
                           
                        }
                        //任务处理完毕以后,将SelectionKey从集合中移除
                        iterator.remove();
                    }
                }
            }
        }
    }
    // 将获取连接的代码抽取到这个类中
    public class AcceptHandler {
    
        public SocketChannel connSocketChannel(SelectionKey selectionKey){
            try {
                //获取到已经就绪的服务端通道
                ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                SocketChannel socketChannel = ssc.accept();
                //设置为非阻塞状态
                socketChannel.configureBlocking(false);
                //把socketChannel注册到选择器上
                socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
                return socketChannel;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

2.获取请求信息并解析

  • 实现步骤

    • 将请求信息封装到HttpRequest类中
    • 在类中定义方法,实现获取请求信息并解析
  • 代码实现

    /**
     * 用来封装请求数据的类
     */
    public class HttpRequest {
        private String method; //请求方式
        private String requestURI; //请求的uri
        private String version;   //http的协议版本
    
        private HashMap<String,String> hm = new HashMap<>();//所有的请求头
    
        //parse --- 获取请求数据 并解析
        public void parse(SelectionKey selectionKey){
            try {
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
    
                StringBuilder sb = new StringBuilder();
                //创建一个缓冲区
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len;
                //循环读取
                while((len = socketChannel.read(byteBuffer)) > 0){
                    byteBuffer.flip();
                    sb.append(new String(byteBuffer.array(),0,len));
                    //System.out.println(new String(byteBuffer.array(),0,len));
                    byteBuffer.clear();
                }
                //System.out.println(sb);
                parseHttpRequest(sb);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //解析http请求协议中的数据
        private void parseHttpRequest(StringBuilder sb) {
            //1.需要把StringBuilder先变成一个字符串
            String httpRequestStr = sb.toString();
            //2.获取每一行数据
            String[] split = httpRequestStr.split("\r\n");
            //3.获取请求行
            String httpRequestLine = split[0];//GET / HTTP/1.1
            //4.按照空格进行切割,得到请求行中的三部分
            String[] httpRequestInfo = httpRequestLine.split(" ");
            this.method = httpRequestInfo[0];
            this.requestURI = httpRequestInfo[1];
            this.version = httpRequestInfo[2];
            //5.操作每一个请求头
            for (int i = 1; i < split.length; i++) {
                String httpRequestHeaderInfo = split[i];//Host: 127.0.0.1:10000
                String[] httpRequestHeaderInfoArr = httpRequestHeaderInfo.split(": ");
                hm.put(httpRequestHeaderInfoArr[0],httpRequestHeaderInfoArr[1]);
            }
    
        }
    
        public String getMethod() {
            return method;
        }
    
        public void setMethod(String method) {
            this.method = method;
        }
    
        public String getRequestURI() {
            return requestURI;
        }
    
        public void setRequestURI(String requestURI) {
            this.requestURI = requestURI;
        }
    
        public String getVersion() {
            return version;
        }
    
        public void setVersion(String version) {
            this.version = version;
        }
    
        public HashMap<String, String> getHm() {
            return hm;
        }
    
        public void setHm(HashMap<String, String> hm) {
            this.hm = hm;
        }
    
        @Override
        public String toString() {
            return "HttpRequest{" +
                    "method='" + method + '\'' +
                    ", requestURI='" + requestURI + '\'' +
                    ", version='" + version + '\'' +
                    ", hm=" + hm +
                    '}';
        }
    }
    

3.给浏览器响应数据

  • 实现步骤

    • 将响应信息封装HttpResponse类中
    • 定义方法,封装响应信息,给浏览器响应数据
  • 代码实现

    public class HttpResponse {
        private String version; //协议版本
        private String status;  //响应状态码
        private String desc;    //状态码的描述信息
    
        //响应头数据
        private HashMap<String, String> hm = new HashMap<>();
    
        private HttpRequest httpRequest;  //我们后面要根据请求的数据,来进行一些判断
    
        //给浏览器响应数据的方法
        public void sendStaticResource(SelectionKey selectionKey) {
            //1.给响应行赋值
            this.version = "HTTP/1.1";
            this.status = "200";
            this.desc = "ok";
            //2.将响应行拼接成一个单独的字符串 // HTTP/1.1 200 ok
            String responseLine = this.version + " " + this.status + " " + this.desc + "\r\n";
    
            //3.给响应头赋值
            hm.put("Content-Type", "text/html;charset=UTF-8");
    
            //4.将所有的响应头拼接成一个单独的字符串
            StringBuilder sb = new StringBuilder();
            Set<Map.Entry<String, String>> entries = hm.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
            }
    
            //5.响应空行
            String emptyLine = "\r\n";
    
            //6.响应行,响应头,响应空行拼接成一个大字符串
            String responseLineStr = responseLine + sb.toString() + emptyLine;
    
            try {
                //7.将上面三个写给浏览器
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                ByteBuffer byteBuffer1 = ByteBuffer.wrap(responseLineStr.getBytes());
                socketChannel.write(byteBuffer1);
    
                //8.单独操作响应体
                //因为在以后响应体不一定是一个字符串
                //有可能是一个文件,所以单独操作
                String s = "哎哟,妈呀,终于写完了.";
                ByteBuffer byteBuffer2 = ByteBuffer.wrap(s.getBytes());
                socketChannel.write(byteBuffer2);
    
                //9.释放资源
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public String getVersion() {
            return version;
        }
    
        public void setVersion(String version) {
            this.version = version;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public HashMap<String, String> getHm() {
            return hm;
        }
    
        public void setHm(HashMap<String, String> hm) {
            this.hm = hm;
        }
    
        public HttpRequest getHttpRequest() {
            return httpRequest;
        }
    
        public void setHttpRequest(HttpRequest httpRequest) {
            this.httpRequest = httpRequest;
        }
    
        @Override
        public String toString() {
            return "HttpResponse{" +
                    "version='" + version + '\'' +
                    ", status='" + status + '\'' +
                    ", desc='" + desc + '\'' +
                    ", hm=" + hm +
                    ", httpRequest=" + httpRequest +
                    '}';
        }
    }
    

4.代码优化

  • 实现步骤

    • 根据请求资源路径不同,响应不同的数据
    • 服务端健壮性处理
    • 访问不存在的资源处理
  • 代码实现

    /**
     * 接收连接的任务处理类
     */
    public class AcceptHandler {
    
        public SocketChannel connSocketChannel(SelectionKey selectionKey){
            try {
                //获取到已经就绪的服务端通道
                ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                SocketChannel socketChannel = ssc.accept();
                //设置为非阻塞状态
                socketChannel.configureBlocking(false);
                //把socketChannel注册到选择器上
                socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
                return socketChannel;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    /**
     * 接收客户端请求的类
     */
    public class HttpServer {
        public static void main(String[] args) throws IOException {
            //1.打开服务端通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //2.让这个通道绑定一个端口
            serverSocketChannel.bind(new InetSocketAddress(10000));
            //3.设置通道为非阻塞
            serverSocketChannel.configureBlocking(false);
            //4.打开一个选择器
            Selector selector = Selector.open();
            //5.绑定选择器和服务端通道
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    
            while(true){
                //6.选择器会监视通道的状态.
                int count = selector.select();
                if(count != 0){
                    //7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
                    //获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while(iterator.hasNext()){
                        //selectionKey 依次表示每一个服务端通道的令牌
                        SelectionKey selectionKey = iterator.next();
                        if(selectionKey.isAcceptable()){
                            //获取连接
                            AcceptHandler acceptHandler = new AcceptHandler();
                            acceptHandler.connSocketChannel(selectionKey);
    
                        }else if(selectionKey.isReadable()){
                            //读取数据
                            HttpRequest httpRequest = new HttpRequest();
                            httpRequest.parse(selectionKey);
                            System.out.println("http请求的数据为 ---->" + httpRequest);
    
                            if(httpRequest.getRequestURI() == null || "".equals(httpRequest.getRequestURI())){
                                selectionKey.channel();
                                continue;
                            }
                            System.out.println("...数据解析完毕,准备响应数据....");
    
                            //响应数据
                            HttpResponse httpResponse = new HttpResponse();
                            httpResponse.setHttpRequest(httpRequest);
                            httpResponse.sendStaticResource(selectionKey);
                        }
                        //任务处理完毕以后,将SelectionKey从集合中移除
                        iterator.remove();
                    }
                }
            }
        }
    }
    /**
     * 用来封装请求数据的类
     */
    public class HttpRequest {
        private String method; //请求方式
        private String requestURI; //请求的uri
        private String version;   //http的协议版本
    
        private HashMap<String,String> hm = new HashMap<>();//所有的请求头
    
        //parse --- 获取请求数据 并解析
        public void parse(SelectionKey selectionKey){
            try {
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
    
                StringBuilder sb = new StringBuilder();
                //创建一个缓冲区
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len;
                //循环读取
                while((len = socketChannel.read(byteBuffer)) > 0){
                    byteBuffer.flip();
                    sb.append(new String(byteBuffer.array(),0,len));
                    //System.out.println(new String(byteBuffer.array(),0,len));
                    byteBuffer.clear();
                }
                //System.out.println(sb);
                parseHttpRequest(sb);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
      
        //解析http请求协议中的数据
        private void parseHttpRequest(StringBuilder sb) {
            //1.需要把StringBuilder先变成一个字符串
            String httpRequestStr = sb.toString();
            if(!(httpRequestStr == null || "".equals(httpRequestStr))){
                //2.获取每一行数据
                String[] split = httpRequestStr.split("\r\n");
                //3.获取请求行
                String httpRequestLine = split[0];//GET / HTTP/1.1
                //4.按照空格进行切割,得到请求行中的三部分
                String[] httpRequestInfo = httpRequestLine.split(" ");
                this.method = httpRequestInfo[0];
                this.requestURI = httpRequestInfo[1];
                this.version = httpRequestInfo[2];
                //5.操作每一个请求头
                for (int i = 1; i < split.length; i++) {
                    String httpRequestHeaderInfo = split[i];//Host: 127.0.0.1:10000
                    String[] httpRequestHeaderInfoArr = httpRequestHeaderInfo.split(": ");
                    hm.put(httpRequestHeaderInfoArr[0],httpRequestHeaderInfoArr[1]);
                }
            }
        }
    
        public String getMethod() {
            return method;
        }
    
        public void setMethod(String method) {
            this.method = method;
        }
    
        public String getRequestURI() {
            return requestURI;
        }
    
        public void setRequestURI(String requestURI) {
            this.requestURI = requestURI;
        }
    
        public String getVersion() {
            return version;
        }
    
        public void setVersion(String version) {
            this.version = version;
        }
    
        public HashMap<String, String> getHm() {
            return hm;
        }
    
        public void setHm(HashMap<String, String> hm) {
            this.hm = hm;
        }
    
        @Override
        public String toString() {
            return "HttpRequest{" +
                    "method='" + method + '\'' +
                    ", requestURI='" + requestURI + '\'' +
                    ", version='" + version + '\'' +
                    ", hm=" + hm +
                    '}';
        }
    }
    /**
     * 用来封装响应数据的类
     */
    public class HttpResponse {
        private String version; //协议版本
        private String status;  //响应状态码
        private String desc;    //状态码的描述信息
    
        //响应头数据
        private HashMap<String, String> hm = new HashMap<>();
    
        private HttpRequest httpRequest;  //我们后面要根据请求的数据,来进行一些判断
    
        //给浏览器响应数据的方法
        public void sendStaticResource(SelectionKey selectionKey) {
            //1.给响应行赋值
            this.version = "HTTP/1.1";
            this.status = "200";
            this.desc = "ok";
    
            //3.给响应头赋值
            //先获取浏览器请求的URI
            String requestURI = this.getHttpRequest().getRequestURI();
            if(requestURI != null){
    
                File file = new File(WEB_APP_PATH + requestURI);
                //判断这个路径是否存在
                if(!file.exists()){
                    this.status = "404";
                    this.desc = "NOT FOUNG";
                }
    
                if("200".equals(this.status)){
                    if("/".equals(requestURI)){
                        hm.put("Content-Type", "text/html;charset=UTF-8");
                    }else if("/favicon.ico".equals(requestURI)){
                        hm.put("Content-Type", "image/x-icon");
                    }else if("/a.txt".equals(requestURI)){
                        hm.put("Content-Type", "text/html;charset=UTF-8");
                    }else if("/1.jpg".equals(requestURI)){
                        hm.put("Content-Type", "image/jpeg");
                    }else if("/1.png".equals(requestURI)){
                        hm.put("Content-Type", "image/png");
                    }
                }else{
                    hm.put("Content-Type", "text/html;charset=UTF-8");
                }
    
            }
    
            //2.将响应行拼接成一个单独的字符串 // HTTP/1.1 200 ok
            String responseLine = this.version + " " + this.status + " " + this.desc + "\r\n";
    
            //4.将所有的响应头拼接成一个单独的字符串
            StringBuilder sb = new StringBuilder();
            Set<Map.Entry<String, String>> entries = hm.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
            }
    
            //5.响应空行
            String emptyLine = "\r\n";
    
            //6.响应行,响应头,响应空行拼接成一个大字符串
            String responseLineStr = responseLine + sb.toString() + emptyLine;
    
            try {
                //7.将上面三个写给浏览器
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                ByteBuffer byteBuffer1 = ByteBuffer.wrap(responseLineStr.getBytes());
                socketChannel.write(byteBuffer1);
    
                //8.单独操作响应体
                //因为在以后响应体不一定是一个字符串
                //有可能是一个文件,所以单独操作
               // String s = "哎哟,妈呀,终于写完了.";
                byte [] bytes = getContent();
                ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes);
                socketChannel.write(byteBuffer2);
    
                //9.释放资源
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static final String WEB_APP_PATH = "mynio\\webapp";
        private byte[] getContent() {
            try {
                //1.获取浏览器请求的URI
                String requestURI = this.getHttpRequest().getRequestURI();
                if(requestURI != null){
    
                    if("200".equals(this.status)){
                        //2.判断一下请求的URI,根据不同的URI来响应不同的东西
                        if("/".equals(requestURI)){
                            String s = "哎哟,妈呀,终于写完了.";
                            return s.getBytes();
                        }else/* if("/favicon.ico".equals(requestURI))*/{
                            //获取一个ico文件
                            FileInputStream fis = new FileInputStream(WEB_APP_PATH + requestURI);
                            //把ico文件变成一个字节数组返回
                            return IOUtils.toByteArray(fis);
                        }
                    }else{
                        return "访问的资源不存在".getBytes();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return new byte[0];
        }
    
        public String getVersion() {
            return version;
        }
    
        public void setVersion(String version) {
            this.version = version;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public HashMap<String, String> getHm() {
            return hm;
        }
    
        public void setHm(HashMap<String, String> hm) {
            this.hm = hm;
        }
    
        public HttpRequest getHttpRequest() {
            return httpRequest;
        }
    
        public void setHttpRequest(HttpRequest httpRequest) {
            this.httpRequest = httpRequest;
        }
    
        @Override
        public String toString() {
            return "HttpResponse{" +
                    "version='" + version + '\'' +
                    ", status='" + status + '\'' +
                    ", desc='" + desc + '\'' +
                    ", hm=" + hm +
                    ", httpRequest=" + httpRequest +
                    '}';
        }
    }
    
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。