使用Socket实现HttpServer(二)

使用Socket实现HttpServer(二)

前面我们使用 Socket 实现了一个简易的 HttpServer,接下来我们将对我们的服务器进行优化:

  • 面向对象的封装
  • 优化线程模型(引入多线程)
  • Request/Response 对象抽象

Step1(面向对象的封装)

对我们之前所写的 HttpServer 进行面向对象封装。

主要封装了 listen() 和 accept() 方法。

package com.fengsir.network;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.function.Function;

/**
 * @Author FengZeng
 * @Date 2022-01-24 13:35
 * @Description TODO
 */
public class Step1Server {

  ServerSocket socketServer;
  Function<String, String> handler;


  public Step1Server(Function<String, String> handler) {
    this.handler = handler;
  }

  /**
   * @param port listen port
   * @throws IOException
   */
  public void listen(int port) throws IOException {
    socketServer = new ServerSocket(port);

    while (true) {
      accept();
    }
  }

  private void accept() throws IOException {
    Socket socket = socketServer.accept();
    System.out.println("a socket created");

    // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
    InputStream inputStream = socket.getInputStream();
    BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream));

    // 按行读取,把内容放到 stringBuilder 中
    StringBuilder requestBuilder = new StringBuilder();
    String line = "";
    // 由于有时候 readLine() 会读取到 null,就会报错 nullException,
    // 所以在这里进行了一点修改
    while (true) {
      line = bfReader.readLine();
      if (line == null || line.isBlank()) {
        break;
      }
      requestBuilder.append(line);
    }
    // 打印请求内容
    String request = requestBuilder.toString();
    System.out.println(request);

    // 封装 response
    BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    String response = this.handler.apply(request);
    bfWriter.write(response);
    // 一定要 flush
    bfWriter.flush();
    socket.close();
  }


  public static void main(String[] args) throws IOException {
    Step1Server step1Server = new Step1Server(res->{
      return "HTTP/1.1 200 ok\n\nGood!\n";
    });
    step1Server.listen(8000);

  }

}

到这里也没有什么太大的问题,不过我们的服务器还是单线程的,如果只是单纯的返回一个字符串,我们的架构也没有问题,如果此时每个请求会去做别的处理,比如去查一下数据库,那么我们的服务器性能就很低了,还会出现服务器拒绝的错误(因为操作系统的 pending Queue 已经满了),所以我们需要进一步优化。

Step2(引入多线程)

这里引入了多线程,不再是一个线程单独在跑了,试了一下 Jemeter 的压测,10000个线程的并发就会把内存拉得很高,主要还是因为创建了线程后 Java 的垃圾回收还没有启动,所以现在一般不会用这种方式去创建线程,后面会引入线程池来优化。

package com.fengsir.network;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.function.Function;

/**
 * @Author FengZeng
 * @Date 2022-01-24 14:01
 * @Description TODO
 */
public class Step2Server {

  ServerSocket socketServer;
  Function<String, String> handler;


  public Step2Server(Function<String, String> handler) {
    this.handler = handler;
  }

  /**
   * @param port listen port
   * @throws IOException
   */
  public void listen(int port) throws IOException {
    socketServer = new ServerSocket(port);

    while (true) {
      accept();
    }
  }

  private void accept() throws IOException {
    // 如果不把 accept 提到这里,那么上面的 while循环会一直创建线程
    // 这也体现了 accept() 是 blocking 的
    Socket socket = socketServer.accept();
    System.out.println("a socket created");
    new Thread(()->{
      try {
        handler(socket);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }).start();
  }

  private void handler(Socket socket) throws IOException {

    // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
    InputStream inputStream = socket.getInputStream();
    BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream));

    // 按行读取,把内容放到 stringBuilder 中
    StringBuilder requestBuilder = new StringBuilder();
    String line = "";
    while (true) {
      line = bfReader.readLine();
      if (line == null || line.isBlank()) {
        break;
      }
      requestBuilder.append(line);
    }
    // 打印请求内容
    String request = requestBuilder.toString();
    System.out.println(request);

    // 封装 response
    BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    String response = this.handler.apply(request);
    bfWriter.write(response);
    // 一定要 flush
    bfWriter.flush();
    socket.close();
  }


  public static void main(String[] args) throws IOException {
    Step2Server step2Server = new Step2Server(res->{
      return "HTTP/1.1 200 ok\n\nGood!\n";
    });
    step2Server.listen(8000);

  }
}

Step3(封装Request/Response)

由于 step2 中,我们使用的是 Function 对象来实现的请求和响应,我们进一步抽象封装,创建 IHandlerInterface 接口:

package com.fengsir.network.step3;

import java.io.IOException;

/**
 * @Author FengZeng
 * @Date 2022-01-24 14:26
 * @Description TODO
 */
@FunctionalInterface
public interface IHandlerInterface {
  /**
   * 处理响应
   * @param request request
   * @param response response
   * @throws IOException
   */
  void handler(Request request, Response response) throws IOException;
}

然后我们创建 Request 类:

package com.fengsir.network.step3;

import org.apache.commons.httpclient.HttpParser;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author FengZeng
 * @Date 2022-01-24 14:27
 * @Description TODO
 */
public class Request {
  // 使用正则表达式提取请求的方法
  static Pattern methodRegex = Pattern.compile("(GET|PUT|POST|DELETE|OPTIONS|TARCE|HEAD)");
  
  private final String body;
  private final String method;
  private final HashMap<String, String> headers;

  public String getBody() {
    return body;
  }

  public String getMethod() {
    return method;
  }

  public HashMap<String, String> getHeaders() {
    return headers;
  }

  public Request(Socket socket) throws IOException {

    // 这是 DataInputStream 和 InputStream 的区别
    // DataInputStream -> primitives(char,float)
    // InputStream -> bytes
    DataInputStream iptStream = new DataInputStream(socket.getInputStream());
    BufferedReader bfReader = new BufferedReader(new InputStreamReader(iptStream));

    // 从第一行读取请求的方法,HttpParser 是我引入的 commons-httpClient 包中的对象
    String methodLine = HttpParser.readLine(iptStream,"UTF-8");
    Matcher matcher = methodRegex.matcher(methodLine);
    matcher.find();
    String method = matcher.group();

    // 解析请求头
    // Content-Type: xxxx
    var headers = HttpParser.parseHeaders(iptStream, "UTF-8");
    HashMap<String, String> headMap = new HashMap<>();
    for (var h : headers) {
      headMap.put(h.getName(), h.getValue());
    }

    // 解析请求体
    var bufferReader = new BufferedReader(new InputStreamReader(iptStream));
    var body = new StringBuilder();

    char[] buffer = new char[1024];
    while (iptStream.available() > 0) {
      bufferReader.read(buffer);
      body.append(buffer);
    }

    this.body = body.toString();
    this.method = method;
    this.headers = headMap;

  }
}

创建 Response 类:

package com.fengsir.network.step3;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.HashMap;

/**
 * @Author FengZeng
 * @Date 2022-01-24 14:27
 * @Description TODO
 */
public class Response {
  Socket socket;
  private int status;
  static HashMap<Integer, String> codeMap;
  public Response(Socket socket) {
    this.socket = socket;
    if (codeMap == null) {
      codeMap = new HashMap<>();
      codeMap.put(200, "ok");
    }
  }

  /**
   * http 标准响应
   * @param msg message
   * @throws IOException
   */
  public void send(String msg) throws IOException {
    this.status = 200;
    var resp = "HTTP/1.1 " + this.status + " " + codeMap.get(this.status) + "\n";
    resp += "\n";
    resp += msg;
    this.sendRaw(resp);
  }

  /**
   * 发送原始的响应
   * @param msg message
   * @throws IOException
   */
  public void sendRaw(String msg) throws IOException {
    var bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    bufferedWriter.write(msg);
    bufferedWriter.flush();
    bufferedWriter.close();
  }


}

到此,我们再重构一下主函数:

package com.fengsir.network.step3;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Author FengZeng
 * @Date 2022-01-24 14:28
 * @Description TODO
 */
public class Step3Server {
  ServerSocket serverSocket;
  IHandlerInterface httpHandler;

  public Step3Server(IHandlerInterface httpHandler) {
    this.httpHandler = httpHandler;
  }
  
  public void listen(int port) throws IOException {
    serverSocket = new ServerSocket(port);
    while (true) {
      this.accept();
    }
  }

  private void accept() throws IOException {
    Socket socket = serverSocket.accept();
    new Thread(() -> {
      try {
        this.handler(socket);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }).start();
  }

  private void handler(Socket socket) throws IOException {
    Request request = new Request(socket);
    Response response = new Response(socket);
    this.httpHandler.handler(request, response);
  }

  public static void main(String[] args) throws IOException {
    var server = new Step3Server((req,resp)->{
      System.out.println(req.getHeaders());
      resp.send("Greetings\n");
    });
    server.listen(8000);

  }


}

可以看到,我们现在的主函数已经很简短了,其实大多数的框架都是从这么一个过程过来的,所以抽象,封装,重构能力是很重要的,优秀的程序员在coding的时候,都会去往这几个方面去想,下一章我打算引入NIO继续优化我们的 Http Server

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值