用Java实现一个简单的反向代理服务器

一: 原理

服务器接受http请求并解析为java对象,如果是静态资源请求则进行处理,是其他请求转发到指定的服务器

二: 代码实现

1.使用nio创建服务器接受http请求

public class HttpServer {

    private ServerSocketChannel server;

    private Selector selector;

    private boolean running = false;

    private ExecutorService executor = Executors.newFixedThreadPool(10);
    public void init(int port) throws IOException {
        this.server = ServerSocketChannel.open();
        this.selector = Selector.open();
        server.configureBlocking(false);
        server.bind(new InetSocketAddress(port));
        server.register(selector, SelectionKey.OP_ACCEPT);
    }


    public void start() {
        if (server == null || selector == null) {
            throw new RuntimeException("服务器未初始化");
        }
        if (running) {
            throw new RuntimeException("重复启动服务器");
        }
        this.running = true;
        Runnable main = () ->{
            while (running) {
                try {
                    selector.select(); //没有事件可选择时会阻塞,它仅在选择了至少一个通道后才会返回
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey current = iterator.next();
                        if (current.isAcceptable()) {
                            System.out.println("有可连接事件");
                            SocketChannel channel = server.accept();
                            //channel.socket().setTcpNoDelay(true);
                            System.out.println("连接成功");
                            channel.configureBlocking(false);
                            channel.register(selector,SelectionKey.OP_READ);
                        }
                        if (current.isReadable()) {
                            //System.out.println("有可读事件");
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            SocketChannel channel = (SocketChannel) current.channel();
                            //System.out.println(channel);
                            int read = 0;
                            StringBuilder builder = new StringBuilder();
                            while ((read = channel.read(byteBuffer)) > 0){
                                byteBuffer.flip();
                                byte[] array = byteBuffer.array();
                                builder.append(new String(array,0,read, StandardCharsets.UTF_8));
                                byteBuffer.clear();
                            }
                            String requestStr = builder.toString();
                            HttpRequestResolver httpRequestResolver = new HttpRequestResolver();
                            httpRequestResolver.resolver(requestStr);
                            if (httpRequestResolver.isLegal()){
                                System.out.println("请求: \n " + requestStr);
                                RequestTask task = new RequestTask(channel, requestStr, httpRequestResolver);
                                System.out.println("已提交处理");
                                executor.execute(task);
                            }
                            //重新注册为读事件
                            //channel.register(selector,SelectionKey.OP_READ);
                        }
                        iterator.remove();
                    }

                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            }
        };
        executor.execute(main);
    }

    public void stop() {
        running = false;
        close();
    }

    private void close() {
        try {
            server.close();
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    public static void main(String[] args) {        HttpServer server1 = new HttpServer();
        try {
            server1.init(8080);
            server1.start();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

}

2. 处理解析http请求

public class HttpRequestResolver {
    private String method;

    private String url;

    private String version;

    private Map<String,String> headers = new HashMap<>();

    private String body;


    private boolean isLegal = false;

    public void resolver(String request){
        if (request.trim().equals("")){
            return;
        }
        String[] lines = request.split("\n");
        //request line
        String[] item = lines[0].split(" ");
        if (item.length != 3){
            throw new RuntimeException("bad request \n" + request);
        }
        method = item[0].trim();
        url = item[1].trim();
        version = item[2].trim();
        //header
        for (int i = 1; i < lines.length; i++){
            if (lines[i] == null || lines[i].trim().equals("")){
                break;
            }
            String[] header = lines[i].split(": ");
            if (header.length != 2){
                throw new RuntimeException("bad request");
            }
            headers.put(header[0],header[1].trim());
        }
        //body
        String last = lines[lines.length - 1];
        body = last.trim().equals("") ? null : last;
        isLegal = true;
    }

    public String getMethod(){
        return method;
    }

    public String getUrl(){
        return url;
    }

    public String getVersion(){
        return version;
    }

    public String getBody(){
        return body;
    }

    public String getHeader(String header){
        return headers.get(header);
    }


    public boolean isLegal(){
        return this.isLegal;
    }

}

3. 处理http请求

/**
 * 处理请求任务
 *
 */
public class RequestTask implements Runnable {

    private static final String errorPath;

    private static final String classPath;

    private static final ExecutorService executorService;


    private final SocketChannel socketChannel;

    private final String requestStr;

    private final HttpRequestResolver requestResolver;


    static {
        classPath = Thread.currentThread().getContextClassLoader().getResource("").getPath().substring(1);
        errorPath = classPath + "html/404.html";
        executorService = Executors.newCachedThreadPool();
    }

    public RequestTask(SocketChannel socketChannel, String requestStr, HttpRequestResolver requestResolver) {
        this.socketChannel = socketChannel;
        this.requestStr = requestStr;
        this.requestResolver = requestResolver;
    }


    @Override
    public void run() {
       synchronized (socketChannel){
           System.out.println("开始处理请求");
           requestResolver.resolver(requestStr);
           String url = requestResolver.getUrl();
           String extension = getExtension(url);
           if (extension == null) {
               //转发请求
               TranspondTask transpondTask = new TranspondTask(socketChannel, requestStr);
               executorService.execute(transpondTask);
           } else {
               String mimeType;
               if ((mimeType = ContentType.getMimeType(extension)) != null) {
                   System.out.println("响应数据");
                   response(url, mimeType);
               } else {
                   response404();
               }
           }
       }
    }

    private void response(String url, String mimeType) {
        try {
            String realityUrl = classPath + "html" + url;
            HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder();
            byte[] content = content(realityUrl);
            System.out.println(content.length);
            String response = httpResponseBuilder.version("HTTP/2")
                    .contentType(mimeType)
                    .contentLength(content.length)
                    .build();
            send(response.getBytes());
            send(content);
            responseFinished();
        } catch (IOException e) {
            response404();
            throw new RuntimeException(e);
        }

    }

    /**
     * 通过文件路径获取文件内容
     *
     * @param path 文件绝对路径
     * @return 文件内容
     * @throws FileNotFoundException fnt
     */
    private byte[] content(String path) throws IOException {
        System.out.println(path);
        byte[] resource = Resource.getResource(path);
        if (resource == null){
            throw new FileNotFoundException();
        }
        return resource;
    }


    /**
     * 发送报文到客户端
     *
     * @param content 报文内容
     * @throws IOException ioe
     */
    private void send(String content) throws IOException {
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
        buffer.put(bytes);
        buffer.flip();
        socketChannel.write(buffer);
    }

    /**
     * 发送请求体到客户端
     *
     * @param body 响应内容
     * @throws IOException ioe
     */
    private void send(byte[] body) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(body.length);
        byteBuffer.put(body);
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        responseFinished();
    }

    /**
     * 响应错误信息
     */
    private void response404() {
        try {
            HttpResponseBuilder httpResponseBuilder = new HttpResponseBuilder();
            byte[] content = content(errorPath);
            String response = httpResponseBuilder
                    .statesCode(404)
                    .contentType("text/html")
                    .contentLength(content.length)
                    .build();
            send(response);
            send(content);
            responseFinished();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 响应完成后调用关闭socketChannel
     * @throws IOException ioe
     */
    private void responseFinished() throws IOException {
        //强制刷新
        socketChannel.socket().getOutputStream().flush();
    }

    private String getExtension(String url) {
        int i = url.lastIndexOf(".");
        if (i == -1) return null;
        else return url.substring(i);
    }

}

4. 将静态资源加载到内存

public class Resource {

    private static final String classPath;

    private static final Map<String,byte[]> resourceMap;


    static {
        classPath = Thread.currentThread().getContextClassLoader().getResource("").getPath().substring(1);
        resourceMap = new HashMap<>();
        String resourcePath = classPath + "/html";
        File file = new File(resourcePath);
        loadResource(file);
    }

    private static void loadResource(File file){
        File[] files = file.listFiles();
        if (files == null) return;
        for(File item : files){
            if (item.isFile()){
                String path = item.getPath();
                System.out.println(path);
                try {
                    byte[] bytes = Files.readAllBytes(Paths.get(path));
                    resourceMap.put(path,bytes);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }else {
                loadResource(item);
            }
        }
    }


    public static byte[] getResource(String filePath){
        filePath = filePath.replaceAll("/", Matcher.quoteReplacement(File.separator));
        return resourceMap.get(filePath);
    }

}

5. 构建响应字符串

public class HttpResponseBuilder {

    private String version = "HTTP/2";

    private int statesCode = 200;

    private String states = "ok";

    private Map<String,String> headers = new HashMap<>();

    private String body;


    public HttpResponseBuilder version(String version){
        this.version = version;
        return this;
    }

    public HttpResponseBuilder statesCode(int statesCode){
        this.statesCode = statesCode;
        return this;
    }

    public HttpResponseBuilder states(String states){
        this.states = states;
        return this;
    }

    public HttpResponseBuilder addHeader(String key, String value){
        headers.put(key,value);
        return this;
    }

    public HttpResponseBuilder body(String body){
        this.body = body;
        return this;
    }

    public HttpResponseBuilder contentType(String mimeType){
        headers.put("content-type",mimeType);
        return this;
    }

    public HttpResponseBuilder contentLength(Integer length){
        headers.put("content-length",length.toString());
        return this;
    }

    public String build(){
        StringBuilder builder = new StringBuilder();
        builder.append(version)
                .append(" ")
                .append(statesCode)
                .append(" ")
                .append(states)
                .append("\n");
        for (Map.Entry<String,String> item : headers.entrySet()){
            builder.append(item.getKey())
                    .append(": ")
                    .append(item.getValue())
                    .append("\n");
        }
        builder.append("\n");
        if (body != null) builder.append(body);
        return builder.toString();
    }


}

7. 执行转发请求的任务

/**
 * 转发请求任务
 */
public class TranspondTask implements Runnable {


    private final SocketChannel socketChannel;

    private final String requestStr;

    public TranspondTask (SocketChannel socketChannel,String requestStr){
        this.socketChannel = socketChannel;
        this.requestStr = requestStr;
    }


    @Override
    public void run() {
        try {
            //目标服务器地址
            SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8081));
            channel.configureBlocking(false);
            byte[] bytes = requestStr.getBytes();
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length);
            byteBuffer.put(bytes);
            byteBuffer.flip();
            channel.write(byteBuffer);
            channel.shutdownOutput();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len =0;
            while ((len = channel.read(buffer)) != -1){
                buffer.flip();
                String s = new String(buffer.array(), 0, len);
                socketChannel.write(buffer);
                buffer.clear();
            }
            channel.close();
            socketChannel.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

8.文件后缀名与content-type对应关系类

public class ContentType {

    private static final Map<String,String> contentType;
   
     static {
        contentType = new HashMap<>();
        contentType.put(".html" , "text/html");
        contentType.put(".css" , "text/css");
        contentType.put(".js" , "text/javascript");
        //............
	}

    public static String getMimeType(String extension){
        return contentType.get(extension);
    }

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值