All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rx.net.http.tunnel.Server Maven / Gradle / Ivy

package org.rx.net.http.tunnel;

import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.rx.core.Tasks;
import org.rx.io.HybridStream;
import org.rx.io.IOStream;
import org.rx.io.Bytes;
import org.rx.net.Sockets;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import static org.rx.core.Extends.quietly;

@Slf4j
public class Server {
    @RequiredArgsConstructor
    class SocksContext {
        private final String appName;
        private final String inboundSocksId;
        private final LinkedBlockingQueue> inboundQueue = new LinkedBlockingQueue<>();
        private volatile boolean outboundReady;
        private volatile Channel outboundChannel;
        private final LinkedBlockingQueue outboundQueue = new LinkedBlockingQueue<>();

        public boolean isBackendActive() {
            return outboundReady && outboundChannel.isActive();
        }

        @SneakyThrows
        public void prepareBackend() {
            synchronized (outboundQueue) {
                MultipartFile stream;
                while ((stream = outboundQueue.poll()) != null) {
                    ByteBuf buf = Bytes.copyInputStream(stream.getInputStream());
                    try {
                        outboundChannel.write(buf);
                    } finally {
                        buf.release();
                    }
                }
                outboundChannel.flush();
                outboundReady = true;
            }
        }

        @SneakyThrows
        public void flushBackend(MultipartFile stream) {
            synchronized (outboundQueue) {
                if (!isBackendActive()) {
                    outboundQueue.offer(stream);
                    return;
                }
            }

            ByteBuf buf = Bytes.copyInputStream(stream.getInputStream());
            try {
                outboundChannel.writeAndFlush(buf);
            } finally {
                buf.release();
            }
        }

        public void closeBackend() {
            outboundReady = false;
            Tasks.setTimeout(() -> {
                Map contextMap = holds.get(appName);
                if (contextMap == null) {
                    return;
                }
                contextMap.remove(inboundSocksId);
            }, timeWaitSeconds * 1000L);
        }
    }

    @RequiredArgsConstructor
    static class BackendHandler extends ChannelInboundHandlerAdapter {
        private final SocksContext socksContext;

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            socksContext.prepareBackend();
        }

        @SneakyThrows
        @Override
        public void channelRead(ChannelHandlerContext outbound, Object msg) {
            HybridStream stream = new HybridStream();
            ByteBuf buf = (ByteBuf) msg;
            try {
                buf.readBytes(stream.getWriter(), buf.readableBytes());
            } finally {
                buf.release();
            }
            socksContext.inboundQueue.offer(stream);
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            socksContext.closeBackend();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            log.error("BackendHandler {}", ctx.channel().remoteAddress(), cause);
            Sockets.closeOnFlushed(ctx.channel());
        }
    }

    private int timeWaitSeconds = 20;
    //appName,socksId
    private final Map> holds = new ConcurrentHashMap<>();

    private SocksContext getSocksContext(SendPack pack) {
        return holds.computeIfAbsent(pack.getAppName(), k -> new ConcurrentHashMap<>())
                .computeIfAbsent(pack.getSocksId(), k -> {
                    SocksContext socksContext = new SocksContext(pack.getAppName(), pack.getSocksId());
                    socksContext.outboundChannel = Sockets.bootstrap(channel -> channel.pipeline().addLast(new BackendHandler(socksContext))).connect(pack.getRemoteEndpoint()).channel();
                    return socksContext;
                });
    }

    public void frontendOffer(SendPack pack) {
        getSocksContext(pack).flushBackend(pack.getBinary());
    }

    public ReceivePack frontendPoll(SendPack pack) {
        ReceivePack receivePack = new ReceivePack(pack.getSocksId());
        SocksContext socksContext = getSocksContext(pack);
        socksContext.inboundQueue.drainTo(receivePack.getBinaries());
        if (receivePack.getBinaries().isEmpty()) {
            IOStream stream = quietly(() -> socksContext.inboundQueue.poll(timeWaitSeconds, TimeUnit.SECONDS));
            if (stream != null) {
                receivePack.getBinaries().add(stream);
                socksContext.inboundQueue.drainTo(receivePack.getBinaries());
            }
        }
        return receivePack;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy