HTTP服务器

需求

  • 编写服务器端代码,实现可以解析浏览器的请求,给浏览器响应数据

环境搭建

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

    1. // 服务端代码
    2. public class HttpServer {
    3. public static void main(String[] args) throws IOException {
    4. //1.打开服务端通道
    5. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    6. //2.让这个通道绑定一个端口
    7. serverSocketChannel.bind(new InetSocketAddress(10000));
    8. //3.设置通道为非阻塞
    9. serverSocketChannel.configureBlocking(false);
    10. //4.打开一个选择器
    11. Selector selector = Selector.open();
    12. //5.绑定选择器和服务端通道
    13. serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    14. while(true){
    15. //6.选择器会监视通道的状态.
    16. int count = selector.select();
    17. if(count != 0){
    18. //7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
    19. //获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
    20. Set<SelectionKey> selectionKeys = selector.selectedKeys();
    21. Iterator<SelectionKey> iterator = selectionKeys.iterator();
    22. while(iterator.hasNext()){
    23. //selectionKey 依次表示每一个服务端通道的令牌
    24. SelectionKey selectionKey = iterator.next();
    25. if(selectionKey.isAcceptable()){
    26. //获取连接
    27. AcceptHandler acceptHandler = new AcceptHandler();
    28. acceptHandler.connSocketChannel(selectionKey);
    29. }else if(selectionKey.isReadable()){
    30. }
    31. //任务处理完毕以后,将SelectionKey从集合中移除
    32. iterator.remove();
    33. }
    34. }
    35. }
    36. }
    37. }
    38. // 将获取连接的代码抽取到这个类中
    39. public class AcceptHandler {
    40. public SocketChannel connSocketChannel(SelectionKey selectionKey){
    41. try {
    42. //获取到已经就绪的服务端通道
    43. ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
    44. SocketChannel socketChannel = ssc.accept();
    45. //设置为非阻塞状态
    46. socketChannel.configureBlocking(false);
    47. //把socketChannel注册到选择器上
    48. socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
    49. return socketChannel;
    50. } catch (IOException e) {
    51. e.printStackTrace();
    52. }
    53. return null;
    54. }
    55. }

获取请求信息并解析

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

    1. /**
    2. * 用来封装请求数据的类
    3. */
    4. public class HttpRequest {
    5. private String method; //请求方式
    6. private String requestURI; //请求的uri
    7. private String version; //http的协议版本
    8. private HashMap<String,String> hm = new HashMap<>();//所有的请求头
    9. //parse --- 获取请求数据 并解析
    10. public void parse(SelectionKey selectionKey){
    11. try {
    12. SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
    13. StringBuilder sb = new StringBuilder();
    14. //创建一个缓冲区
    15. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    16. int len;
    17. //循环读取
    18. while((len = socketChannel.read(byteBuffer)) > 0){
    19. byteBuffer.flip();
    20. sb.append(new String(byteBuffer.array(),0,len));
    21. //System.out.println(new String(byteBuffer.array(),0,len));
    22. byteBuffer.clear();
    23. }
    24. //System.out.println(sb);
    25. parseHttpRequest(sb);
    26. } catch (IOException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. //解析http请求协议中的数据
    31. private void parseHttpRequest(StringBuilder sb) {
    32. //1.需要把StringBuilder先变成一个字符串
    33. String httpRequestStr = sb.toString();
    34. //2.获取每一行数据
    35. String[] split = httpRequestStr.split("\r\n");
    36. //3.获取请求行
    37. String httpRequestLine = split[0];//GET / HTTP/1.1
    38. //4.按照空格进行切割,得到请求行中的三部分
    39. String[] httpRequestInfo = httpRequestLine.split(" ");
    40. this.method = httpRequestInfo[0];
    41. this.requestURI = httpRequestInfo[1];
    42. this.version = httpRequestInfo[2];
    43. //5.操作每一个请求头
    44. for (int i = 1; i < split.length; i++) {
    45. String httpRequestHeaderInfo = split[i];//Host: 127.0.0.1:10000
    46. String[] httpRequestHeaderInfoArr = httpRequestHeaderInfo.split(": ");
    47. hm.put(httpRequestHeaderInfoArr[0],httpRequestHeaderInfoArr[1]);
    48. }
    49. }
    50. public String getMethod() {
    51. return method;
    52. }
    53. public void setMethod(String method) {
    54. this.method = method;
    55. }
    56. public String getRequestURI() {
    57. return requestURI;
    58. }
    59. public void setRequestURI(String requestURI) {
    60. this.requestURI = requestURI;
    61. }
    62. public String getVersion() {
    63. return version;
    64. }
    65. public void setVersion(String version) {
    66. this.version = version;
    67. }
    68. public HashMap<String, String> getHm() {
    69. return hm;
    70. }
    71. public void setHm(HashMap<String, String> hm) {
    72. this.hm = hm;
    73. }
    74. @Override
    75. public String toString() {
    76. return "HttpRequest{" +
    77. "method='" + method + '\'' +
    78. ", requestURI='" + requestURI + '\'' +
    79. ", version='" + version + '\'' +
    80. ", hm=" + hm +
    81. '}';
    82. }
    83. }

给浏览器响应数据

  • 实现步骤
    • 将响应信息封装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 +
                 '}';
     }
    }
    

代码优化

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

    /**
    * 接收连接的任务处理类
    */
    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 +
                 '}';
     }
    }