Socket实现HttpServer

HTTPServer

1. HTTPServer从0到1

1.1 socket基础

通过ServerSocket绑定端口,提供TCP服务

//通过创建ServerSocket对象,绑定端口
ServerSoker serverSocket = new ServerSocket(port,ip);
//通过accept()监听客户端请求
serverSocket.accept();
//接受请求数据
socket.getInputStream();
//返回响应数据
out = socker.getOutputStream();
out.print(response);
out.flush();
//关闭连接
socket.close();
serverSocket.close();

1.2 http协议

http请求信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zCqc6bzH-1589806934847)(E:\笔记\00.jpg)]

http响应信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkacNJyM-1589806934849)(E:\笔记\01.jpg)]

1.3 http服务器设计

  1. 请求数据解析

    获取Socket中的数据,封装成Request对象,解析为http请求

    @Data
    public static class Request {
        /**
         * 请求方法 GET/POST/PUT/DELETE/OPTION...
         */
        private String method;
        /**
         * 请求的uri
         */
        private String uri;
        /**
         * http版本
         */
        private String version;
        /**
         * 请求头
         */
        private Map<String, String> headers;
        /**
         * 请求参数相关
         */
        private String message;
    }
    

    http解析过程包括:请求行,请求头和正文

    • 请求行的解析:请求方法+URI+HTTP版本

      /**
       * 根据标准的http协议,解析请求行
       *
       * @param reader
       * @param request
       */
      private static void decodeRequestLine(BufferedReader reader, Request request) throws IOException {
          String[] strs = StringUtils.split(reader.readLine(), " ");
          assert strs.length == 3;
          request.setMethod(strs[0]);
          request.setUri(strs[1]);
          request.setVersion(strs[2]);
      }
      
    • 请求头的解析:请求头为key:value的形式

      /**
       * 根据标准http协议,解析请求头
       *
       * @param reader
       * @param request
       * @throws IOException
       */
      private static void decodeRequestHeader(BufferedReader reader, Request request) throws IOException {
          Map<String, String> headers = new HashMap<>(16);
          String line = reader.readLine();
          String[] kv;
          while (!"".equals(line)) {
              kv = StringUtils.split(line, ":");
              assert kv.length == 2;
              headers.put(kv[0].trim(), kv[1].trim());
              line = reader.readLine();
          }
      
          request.setHeaders(headers);
      }
      
    • 正文的解析:为空或非空

      /**
       * 根据标注http协议,解析正文
       *
       * @param reader
       * @param request
       * @throws IOException
       */
      private static void decodeRequestMessage(BufferedReader reader, Request request) throws IOException {
          //获取请求头中Content-Length的长度,新建长度为contentLen的char[]数组
          int contentLen = Integer.parseInt(request.getHeaders().getOrDefault("Content-Length", "0"));
          if (contentLen == 0) {
              // 表示没有message,直接返回
              // 如get/options请求就没有message
              return;
          }
      
          char[] message = new char[contentLen];
          reader.read(message);
          request.setMessage(new String(message));
      }
      

      最后将三种解析封装起来,完成request的解析

      /**
       * http的请求可以分为三部分
       *
       * 第一行为请求行: 即 方法 + URI + 版本
       * 第二部分到一个空行为止,表示请求头
       * 空行
       * 第三部分为接下来所有的,表示发送的内容,message-body;其长度由请求头中的 Content-Length 决定
       *
       * 几个实例如下
       *
       * @param reqStream
       * @return
       */
      public static Request parse2request(InputStream reqStream) throws IOException {
          BufferedReader httpReader = new BufferedReader(new InputStreamReader(reqStream, "UTF-8"));
          Request httpRequest = new Request();
          decodeRequestLine(httpReader, httpRequest);
          decodeRequestHeader(httpReader, httpRequest);
          decodeRequestMessage(httpReader, httpRequest);
          return httpRequest;
      }
      
  2. 请求任务HTTPTask

    为了支持并发,每个请求单独分配一个任务,对于ServerSocket,接收到了一个请求,就创建一个HTTPTaskr任务来实现HTTP通信

    HTTPTask的作用

    • 从请求中获取数据

    • 响应请求

    • 封装结果并返回

      public class HttpTask implements Runnable {
          private Socket socket;
      
          public HttpTask(Socket socket) {
              this.socket = socket;
          }
      
          @Override
          public void run() {
              if (socket == null) {
                  throw new IllegalArgumentException("socket can't be null.");
              }
      
              try {
                  //获取socket的输出流
                  OutputStream outputStream = socket.getOutputStream();
                  PrintWriter out = new PrintWriter(outputStream);
      
                  //获取HTTP请求并解析
                  HttpMessageParser.Request httpRequest = HttpMessageParser.parse2request(socket.getInputStream());
                  try {
                      // 根据请求结果进行响应,省略返回
                      String result = ...;
                      //org.apache.http.io.HttpMessageParser
                      String httpRes = HttpMessageParser.buildResponse(httpRequest, result);
                      out.print(httpRes);
                  } catch (Exception e) {
                      String httpRes = HttpMessageParser.buildResponse(httpRequest, e.toString());
                      out.print(httpRes);
                  }
                  out.flush();
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  try {
                      socket.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      

      演示案例

      @Data
      public static class Response {
          private String version;
          private int code;
          private String status;
      
          private Map<String, String> headers;
      
          private String message;
      }
      
      public static String buildResponse(Request request, String response) {
          //设置响应信息
          Response httpResponse = new Response();
          httpResponse.setCode(200);
          httpResponse.setStatus("ok");
          httpResponse.setVersion(request.getVersion());
      
          Map<String, String> headers = new HashMap<>();
          headers.put("Content-Type", "application/json");
          headers.put("Content-Length", String.valueOf(response.getBytes().length));
          httpResponse.setHeaders(headers);
      
          httpResponse.setMessage(response);
      
          StringBuilder builder = new StringBuilder();
          buildResponseLine(httpResponse, builder);
          buildResponseHeaders(httpResponse, builder);
          buildResponseMessage(httpResponse, builder);
          return builder.toString();
      }
      
      //设置响应行
      private static void buildResponseLine(Response response, StringBuilder stringBuilder) {
          stringBuilder.append(response.getVersion()).append(" ").append(response.getCode()).append(" ")
                  .append(response.getStatus()).append("\n");
      }
      //设置响应头
      private static void buildResponseHeaders(Response response, StringBuilder stringBuilder) {
          for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) {
              stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
          }
          stringBuilder.append("\n");
      }
      //设置响应正文
      private static void buildResponseMessage(Response response, StringBuilder stringBuilder) {
          stringBuilder.append(response.getMessage());
      }
      
  3. HTTP服务器搭建

    创建ServerSocket,绑定接受请求

    public class BasicHttpServer {
        //创建线程池
        private static ExecutorService bootstrapExecutor = Executors.newSingleThreadExecutor();
        private static ExecutorService taskExecutor;
        private static int PORT = 8999;
    
        static void startHttpServer() {
            int nThreads = Runtime.getRuntime().availableProcessors();
            //创建线程池
            taskExecutor =
                    new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100),
                            new ThreadPoolExecutor.DiscardPolicy());
    
            while (true) {
                try {
                    //绑定接受请求端口
                    ServerSocket serverSocket = new ServerSocket(PORT);
                    bootstrapExecutor.submit(new ServerThread(serverSocket));
                    break;
                } catch (Exception e) {
                    try {
                        //重试
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
    
            bootstrapExecutor.shutdown();
        }
    
        private static class ServerThread implements Runnable {
    
            private ServerSocket serverSocket;
    
            public ServerThread(ServerSocket s) throws IOException {
                this.serverSocket = s;
            }
    
            @Override
            public void run() {
                while (true) {
                    try {
                        Socket socket = this.serverSocket.accept();
                        HttpTask eventTask = new HttpTask(socket);
                        taskExecutor.submit(eventTask);
                    } catch (Exception e) {
                        e.printStackTrace();
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                        }
                    }
                }
            }
        }
    }
    
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值