http代理服务器(三)fiddler【重点】


 

 0 host 头

https://www.jianshu.com/p/5332f470e33c

https://zhuanlan.zhihu.com/p/33264232

 

1 将19年的程序24netty(二十)http代理服务器【重点】再拿出来居然能直接用 

 

整理一下

package com.jds.test.httpproxy.miniserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.*;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;


public class HttpServerJob implements Runnable {

    final EventLoopGroup CLIENT_BOSS_LOOP_GROUP = new NioEventLoopGroup(1);
    final EventLoopGroup CLIENT_WORKER_LOOP_GROUP = new NioEventLoopGroup(4);

    public static void main(String[] args) throws Exception {
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
    }

    public HttpServerJob(int port, String url, String auth) {
        this.port = port;
        this.url = url;
        this.auth = auth;
    }

    private int port;
    private String url;
    private ReqQueue reqQueue = new ReqQueue();
    private String auth;

    @Override
    public void run() {
        startHttpServer();
    }

    public void startHttpServer() {
        try {
            ServerBootstrap bs = new ServerBootstrap();
            bs.group(CLIENT_BOSS_LOOP_GROUP, CLIENT_WORKER_LOOP_GROUP);
            bs.channel(NioServerSocketChannel.class);
            bs.childHandler(new HttpServerInitializer());

            ChannelFuture future = bs.bind(this.port).sync();

            System.out.println("http server start at " + port);
            reqQueue.startConsumer();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            CLIENT_BOSS_LOOP_GROUP.shutdownGracefully();
            CLIENT_WORKER_LOOP_GROUP.shutdownGracefully();
        }
    }

    private class HttpServerInitializer extends ChannelInitializer<SocketChannel> {

        @Override
        public void initChannel(SocketChannel ch) throws Exception {

            ChannelPipeline pipeline = ch.pipeline();

            pipeline.addLast(new HttpServerCodec());
            pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
            pipeline.addLast(new BodyToResponseEncoder());
            pipeline.addLast(new RequestToBodyDecoder());
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            super.exceptionCaught(ctx, cause);
        }
    }

    public static class ReqQueue {

        private BlockingQueue<OriginHttp> arrayBlockingQueue = new LinkedBlockingDeque<OriginHttp>(100);

        public void startConsumer() {
            for(int i=0; i<10; ++i) {
                new Thread(new Customer()).start();
            }
        }

        public void put(OriginHttp originHttp) {
            try {
                arrayBlockingQueue.put(originHttp);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private class Customer implements Runnable {

            @Override
            public void run() {
                try {
                    while (true){
                        OriginHttp originHttp = arrayBlockingQueue.take();
                        ProxySender.send(originHttp, originHttp.getUrl());
                        Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private class RequestToBodyDecoder extends MessageToMessageDecoder<FullHttpRequest> {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("active");
            super.channelActive(ctx);
        }

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest, List<Object> list) throws Exception {

            HttpMethod httpMethod = fullHttpRequest.getMethod();
            String uri = fullHttpRequest.getUri();
            HttpHeaders httpHeaders = fullHttpRequest.headers();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, String> entry : httpHeaders.entries()) {
                map.put(entry.getKey(), entry.getValue());
            }

            ByteBuf msg = fullHttpRequest.content();
            byte[] bs = new byte[msg.readableBytes()];
            msg.readBytes(bs);

            OriginHttp originHttp = new OriginHttp(httpMethod.name(), uri, map, bs, channelHandlerContext);
            originHttp.setUrl(url);
            originHttp.setAuth(auth);
            reqQueue.put(originHttp);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            super.exceptionCaught(ctx, cause);
        }
    }

    public static class BodyToResponseEncoder extends MessageToMessageEncoder<ResHttp> {

        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, ResHttp resHttp, List<Object> list) throws Exception {

            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.valueOf(resHttp.getRet()),
                    Unpooled.wrappedBuffer(resHttp.getBody()));

            Map<String, String> headers = resHttp.getHeaders();
            for(Map.Entry<String, String> entry : headers.entrySet()) {
                response.headers().set(entry.getKey(), entry.getValue());

                if(entry.getKey().equals("Set-Cookie")) {
                    response.headers().set(entry.getKey(), entry.getValue().replace("Secure", "").replace("HttpOnly", ""));
                }
            }

            response.headers().remove("X-Frame-Options");
            response.headers().set("testproxy", "hhh");

            /**
             * one of content_length and chunked in response header neccesary in http long connections
             */
            if(!response.headers().contains("Transfer-Encoding") && !"chunked".equals(response.headers().get("Transfer-Encoding")))
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
//        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaders.Names.CONTENT_TYPE);

            list.add(response);
        }
    }

    public static class OriginHttp {
        private String url;
        private String method;
        private String uri;
        private Map<String, String> headers;
        private byte [] body;
        ChannelHandlerContext context;
        private String auth;

        public OriginHttp(String method, String uri, Map<String, String> headers, byte [] body, ChannelHandlerContext context) {
            this.method = method;
            this.uri = uri;
            this.headers = headers;
            this.body = body;
            this.context = context;
        }

        public String getMethod() {
            return method;
        }

        public String getUri() {
            return uri;
        }

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

        public byte[] getBody() {
            return body;
        }

        public ChannelHandlerContext getContext() {
            return context;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getAuth() {
            return auth;
        }

        public void setAuth(String auth) {
            this.auth = auth;
        }
    }

    private static class ProxySender {

        public static void send(OriginHttp originHttp, String dir) {

            String method = originHttp.getMethod();
            String uri = originHttp.getUri();
            String url = new StringBuilder(dir).append(uri).toString();
            System.out.println(url);

            HttpUriRequest httpUriRequest = null;

            if ("GET".equals(method)) {
                httpUriRequest = new HttpGet(url);
            } else if ("POST".equals(method)) {
                httpUriRequest = new HttpPost(url);

                HttpPost httpPost = (HttpPost) httpUriRequest;
                try {
                    String st = new String(originHttp.getBody());
                    httpPost.setEntity(new StringEntity(st));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

            Map<String, String> headers = originHttp.getHeaders();
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                String val = entry.getValue();
                httpUriRequest.addHeader(entry.getKey(), val);
            }

//        httpUriRequest.addHeader("Cookie", cookie);
            httpUriRequest.removeHeaders("Content-Length");
            httpUriRequest.removeHeaders("Origin");
            httpUriRequest.removeHeaders("Referer");
            httpUriRequest.removeHeaders("Host");
            httpUriRequest.addHeader("Origin", dir);
            httpUriRequest.addHeader("Referer", dir);
            httpUriRequest.addHeader("sm_user", originHttp.getAuth());
            //    httpUriRequest.addHeader("Host", dir);

            CloseableHttpClient httpClient = HttpClientFactory.createSSLClientDefault();
            org.apache.http.HttpResponse response = null;
            try {
                response = httpClient.execute(httpUriRequest);

                Map<String, String> map = new HashMap<>();
                Header[] headersRes = response.getAllHeaders();
                for (Header header : headersRes) {
                    map.put(header.getName(), header.getValue());
                }

                int ret = response.getStatusLine().getStatusCode();

                HttpEntity responseEntity = response.getEntity();
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                if(responseEntity != null) {
                    InputStream inputStream = responseEntity.getContent();
                    byte[] bytes = new byte[1024];
                    int i = 0;
                    while ((i = inputStream.read(bytes)) != -1) {
                        byteArrayOutputStream.write(bytes, 0, i);
                    }
                }

                ResHttp resHttp = new ResHttp(ret, map, byteArrayOutputStream.toByteArray());

                ChannelFuture channelFuture = originHttp.getContext().writeAndFlush(resHttp);
                channelFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {

                        /**
                         * to use http short connections by this line
                         */
                        //    future.channel().close();

                        if (!future.isSuccess()) {
                            future.cause().printStackTrace();
                            future.channel().close();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }
        }
    }

    private static class HttpClientFactory {

        public static CloseableHttpClient createSSLClientDefault() {
            try {
                SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        return true;
                    }
                }).build();

                HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);

                return HttpClients.custom().setSSLSocketFactory(sslsf).build();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return HttpClients.createDefault();
        }
    }

    private static class ResHttp {
        private int ret;
        private Map<String, String> headers;
        private byte [] body;

        public ResHttp(int ret, Map headers, byte [] body) {
            this.ret = ret;
            this.headers = headers;
            this.body = body;
        }

        public int getRet() {
            return ret;
        }

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

        public byte[] getBody() {
            return body;
        }
    }
}

  

1.1 发现比不用代理快,因为chrome有6个连接限制(这句话是错的,因为浏览头在chrome,mq线程未必有用)

1.2 局域网访问出问题,session任意时刻都过期

判断set-cookie 头出问题,跟踪chrome network,发现 response的set-cookie头旁边:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the Secure attribute but was not recerved over a secure connection
https://blog.csdn.net/gusijin/article/details/120541938

Add yellow:

 

    private static class BodyToResponseEncoder extends MessageToMessageEncoder<ResHttp> {

        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, ResHttp resHttp, List<Object> list) throws Exception {

            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.valueOf(resHttp.getRet()),
                    Unpooled.wrappedBuffer(resHttp.getBody()));

            Map<String, String> headers = resHttp.getHeaders();
            for(Map.Entry<String, String> entry : headers.entrySet()) {
                response.headers().set(entry.getKey(), entry.getValue());

                if(entry.getKey().equals("Set-Cookie")) {
                    response.headers().set(entry.getKey(), entry.getValue().replace("Secure", "").replace("HttpOnly", ""));
                }
            }

            response.headers().remove("X-Frame-Options");

            /**
             * one of content_length and chunked in response header neccesary in http long connections
             */
           
if(!response.headers().contains("Transfer-Encoding") && !"chunked".equals(response.headers().get("Transfer-Encoding")))
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
//        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaders.Names.CONTENT_TYPE);

           
list.add(response);
        }
    }

 

 

 

2 开发fiddler,透明代理

https://blog.csdn.net/dotalee/article/details/77838676

https://www.jianshu.com/p/aaa211c11a27

package com.jds.test.httpproxy.miniserver;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class ProxyNoHttpsDecode {

    public static void main(String []jj) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup(2);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<Channel>() {

                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            ch.pipeline().addLast("httpCodec",new HttpServerCodec());
                            ch.pipeline().addLast("httpObject",new HttpObjectAggregator(65536));
                            ch.pipeline().addLast("serverHandle",new HttpProxyServerHandle());
                        }
                    });
            ChannelFuture f = b
                    .bind(1999)
                    .sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    public static class HttpProxyServerHandle extends ChannelInboundHandlerAdapter {

        private ChannelFuture cf;
        private String host;
        private int port;

        @Override
        public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
            if (msg instanceof FullHttpRequest) {
                FullHttpRequest request = (FullHttpRequest) msg;
                String host = request.headers().get("host");
                String[] temp = host.split(":");
                int port = 80;
                if (temp.length > 1) {
                    port = Integer.parseInt(temp[1]);
                } else {
                    if (request.getUri().indexOf("https") == 0) {
                        port = 443;
                    }
                }
                this.host = temp[0];
                this.port = port;
                if ("CONNECT".equalsIgnoreCase(request.getMethod().name())) {//HTTPS建立代理握手
                    HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                    ctx.writeAndFlush(response);
                    System.out.println("--------------------cc----------------------");
                    ctx.pipeline().remove("httpCodec");
                    ctx.pipeline().remove("httpObject");
                    return;
                }

                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(ctx.channel().eventLoop())
                        .channel(ctx.channel().getClass())
                        .handler(new HttpProxyInitializer(ctx.channel()));

                ChannelFuture cf = bootstrap.connect(temp[0], port);
                cf.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (future.isSuccess()) {
                            future.channel().writeAndFlush(msg);
                        } else {
                            ctx.channel().close();
                        }
                    }
                });
//            ChannelFuture cf = bootstrap.connect(temp[0], port).sync();
//            cf.channel().writeAndFlush(request);
            } else { // https 只转发数据,不做处理
                if (cf == null) {
                    System.out.println("------------------------------------------");
                    Bootstrap bootstrap = new Bootstrap();
                    bootstrap.group(ctx.channel().eventLoop())
                            .channel(ctx.channel().getClass())
                            .handler(new ChannelInitializer() {

                                @Override
                                protected void initChannel(Channel ch) throws Exception {
                                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx0, Object msg) throws Exception {
                                            ctx.channel().writeAndFlush(msg);
                                        }
                                    });
                                }
                            });
                    cf = bootstrap.connect(host, port);
                    cf.addListener(new ChannelFutureListener() {
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isSuccess()) {
                                future.channel().writeAndFlush(msg);
                            } else {
                                ctx.channel().close();
                            }
                        }
                    });
                } else {
                    cf.channel().writeAndFlush(msg);
                }
            }
        }


    }

    public static class HttpProxyInitializer extends ChannelInitializer{

        private Channel clientChannel;

        public HttpProxyInitializer(Channel clientChannel) {
            this.clientChannel = clientChannel;
        }

        @Override
        protected void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(new HttpClientCodec());
            ch.pipeline().addLast(new HttpObjectAggregator(6553600));
            ch.pipeline().addLast(new HttpProxyClientHandle(clientChannel));
        }
    }

    public static class HttpProxyClientHandle extends ChannelInboundHandlerAdapter {

        private Channel clientChannel;

        public HttpProxyClientHandle(Channel clientChannel) {
            this.clientChannel = clientChannel;
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            FullHttpResponse response = (FullHttpResponse) msg;
            response.headers().add("test","from proxy");
            clientChannel.writeAndFlush(msg);
        }
    }
}

  

目标服务器取首次Connect http request header 的 host

 

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.0.29.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.8</version>
        </dependency>
    </dependencies>

  

 

3 明文代理

3.0 原理:

fiddler原理+fiddler为什么抓chrome而不能抓curl和httpclient?fiddler为什么能篡改报文?

https原理

3.1 先跟着 https://www.cnblogs.com/javalinux/p/16136628.html(netty https hsc123)吧netty https搭起来

keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass hsc123 -storepass hsc123 -keystore local.jks

3.2 结合2的CONNECT METHOD,搭建https代理

package com.jds.test.httpproxy.miniserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;


import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.netty.handler.codec.http.*;


public class ProxyHttpsDecode {

    private static HttpServerJob.ReqQueue reqQueue = new HttpServerJob.ReqQueue();

    public static void start(final int port) throws Exception {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        try {
            serverBootstrap.channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .group(boss, worker)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            /**
                             * 不加不能直接访问
                             */
//                            SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
//                            sslEngine.setUseClientMode(false);
//                            pipeline.addLast("SslHandler", new SslHandler(sslEngine));
                            pipeline.addLast("HttpServerCodec", new HttpServerCodec());
                            pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(Integer.MAX_VALUE));
                            pipeline.addLast("BodyToResponseEncoder", new HttpServerJob.BodyToResponseEncoder());
                            pipeline.addLast("RequestToBodyDecoder", new RequestToBodyDecoder());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("http server start at " + port);
            reqQueue.startConsumer();
            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        start(1999);
    }

    private static class SSLContextFactory {

        public static SSLContext getSslContext() throws Exception {
            char[] passArray = "hsc123".toCharArray();
            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            KeyStore ks = KeyStore.getInstance("JKS");
            FileInputStream inputStream = new FileInputStream("C:\\Users\\xxx\\local.jks");
            ks.load(inputStream, passArray);
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, passArray);
            sslContext.init(kmf.getKeyManagers(), null, null);
            inputStream.close();
            return sslContext;

        }
    }

    private static class RequestToBodyDecoder extends MessageToMessageDecoder<FullHttpRequest> {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("active");
            super.channelActive(ctx);
        }

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest, List<Object> list) throws Exception {

            if ("CONNECT".equalsIgnoreCase(fullHttpRequest.getMethod().name())) {//HTTPS建立代理握手
                HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                channelHandlerContext.writeAndFlush(response);
                System.out.println("ssl request");
                SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
                sslEngine.setUseClientMode(false);
                channelHandlerContext.pipeline().addFirst("SslHandler", new SslHandler(sslEngine));
                return;
            }

            /**
             * 不能直接访问
             */
//            FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//            httpResponse.content().writeBytes("https".getBytes());
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_LENGTH, httpResponse.content().readableBytes());
//            channelHandlerContext.writeAndFlush(httpResponse);
//            if(true)return;

            HttpMethod httpMethod = fullHttpRequest.getMethod();
            String uri = fullHttpRequest.getUri();
            HttpHeaders httpHeaders = fullHttpRequest.headers();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, String> entry : httpHeaders.entries()) {
                map.put(entry.getKey(), entry.getValue());
            }

            ByteBuf msg = fullHttpRequest.content();
            byte[] bs = new byte[msg.readableBytes()];
            msg.readBytes(bs);

            HttpServerJob.OriginHttp originHttp = new HttpServerJob.OriginHttp(httpMethod.name(), uri, map, bs, channelHandlerContext);

            String host = fullHttpRequest.headers().get("host");

            String url = "https://" + host + "/";
            String auth = "xxx";

            originHttp.setUrl(url);
            originHttp.setAuth(auth);
            reqQueue.put(originHttp);

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            super.exceptionCaught(ctx, cause);
        }
    }

}

  

可以看到 目标chrome的证书已经是localhost签发的 

http=127.0.0.1:1999;https=127.0.0.1:1999

 

3.3 有很多js css无法通过代理加载

 

 

3.4

3.4.1 发现这个代理对公司的tomcat,只需要输入一次thisisunsafe就能100%跑出来,但在家里网络对百度、简书就不行,总有一些文件红了

3.4.2 实践中,发现每次输入另一个域名都要输入一次thisisunsafe,所以这个自签名证书并不是输入一次信任就行了

3.4.3 结合3.4.2判断是这个问题,找到这些红的文件,发现他们跟主域名(即输入过thisisunsafe的)都不同,这些文件在浏览器发出请求时都没有机会输,所以请求失败

3.4.4 公司网站因为都是内部js(/xxx/js)故没有暴露这个问题,公网网站乱七八糟的外部资源多

 

3.5 证书

keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass hsc123 -storepass hsc123 -keystore local.jks

JoycedeMacBook:work joyce$ cd /Users/joyce/work/MyTest/HttpProxy/
JoycedeMacBook:HttpProxy joyce$ keytool -importkeystore -srckeystore local.jks -destkeystore local.jks -deststoretype pkcs12
keytool 错误: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big.
JoycedeMacBook:HttpProxy joyce$ ll
total 24
drwxr-xr-x 7 joyce staff 224 Aug 11 22:52 ./
drwxr-xr-x 23 joyce staff 736 Aug 11 22:46 ../
-rw-r--r-- 1 joyce staff 1196 Aug 11 22:48 HttpProxy.iml
-rw-r--r-- 1 joyce staff 2066 Aug 11 22:52 local.jks
-rw-r--r-- 1 joyce staff 2154 Aug 11 22:46 pom.xml
drwxr-xr-x 3 joyce staff 96 Aug 11 22:45 src/
drwxr-xr-x 4 joyce staff 128 Aug 11 22:52 target/
JoycedeMacBook:HttpProxy joyce$ keytool -importkeystore -srckeystore local.jks -destkeystore local0.jks -deststoretype pkcs12
输入目标密钥库口令:
再次输入新口令:
输入源密钥库口令:
已成功导入别名 mykey 的条目。
已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消
JoycedeMacBook:HttpProxy joyce$ ll
total 32
drwxr-xr-x 8 joyce staff 256 Aug 15 11:48 ./
drwxr-xr-x 23 joyce staff 736 Aug 11 22:46 ../
-rw-r--r-- 1 joyce staff 1196 Aug 11 22:48 HttpProxy.iml
-rw-r--r-- 1 joyce staff 2066 Aug 11 22:52 local.jks
-rw-r--r-- 1 joyce staff 2402 Aug 15 11:48 local0.jks
-rw-r--r-- 1 joyce staff 2154 Aug 11 22:46 pom.xml
drwxr-xr-x 3 joyce staff 96 Aug 11 22:45 src/
drwxr-xr-x 4 joyce staff 128 Aug 11 22:52 target/
JoycedeMacBook:HttpProxy joyce$ rm local.jks
JoycedeMacBook:HttpProxy joyce$ mv local0.jks local.jks
JoycedeMacBook:HttpProxy joyce$ keytool -list -rfc --keystore local.jks | openssl x509 -inform pem -pubkey
输入密钥库口令: hsc123
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkJIdaFvYYFa3lX3HJ49J
KBXSXjnyDwTba7Tt08bTnZUS8tszX6Id1uYMCvKCCcM4JZWeFac5k16wtq2uL6dN
Tz64ZSoQpWNo3/9vW9EBNjtc4fI0gDISkJEU7rDNh5QetMawuI8TBF0UZ7TxhtRj
LIDSHsOcGW8r6UHxpI85yz4/5haVy5WBB01e0f+CtkBP0RIn87fgXKs6THLXYPUv
tc6Q9HpEVza9UuS9JItjk88Ti0Sl6uu9uG8J8dddH+LYXX7rsp7Bz5gdNZTlDfMb
7OjlpIlY+6zGayz6kT9aFPJ8aKs1xtkRk77JHFMqUYGGE+1dUhVkXSJULxmRJnHC
2QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIICxzCCAa+gAwIBAgIEPpwDEDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls
b2NhbGhvc3QwHhcNMjIwODExMTQ1MjAyWhcNMjMwODExMTQ1MjAyWjAUMRIwEAYD
VQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ
kh1oW9hgVreVfccnj0koFdJeOfIPBNtrtO3TxtOdlRLy2zNfoh3W5gwK8oIJwzgl
lZ4VpzmTXrC2ra4vp01PPrhlKhClY2jf/29b0QE2O1zh8jSAMhKQkRTusM2HlB60
xrC4jxMEXRRntPGG1GMsgNIew5wZbyvpQfGkjznLPj/mFpXLlYEHTV7R/4K2QE/R
Eifzt+BcqzpMctdg9S+1zpD0ekRXNr1S5L0ki2OTzxOLRKXq6724bwnx110f4thd
fuuynsHPmB01lOUN8xvs6OWkiVj7rMZrLPqRP1oU8nxoqzXG2RGTvskcUypRgYYT
7V1SFWRdIlQvGZEmccLZAgMBAAGjITAfMB0GA1UdDgQWBBQcyvPPDD289Q6lB+8M
75J+xa7hoDANBgkqhkiG9w0BAQsFAAOCAQEAAfPRZMh20IYziT09cWIxgKoN5h8X
RG3vNYiXyf8B8WPfRVNPX5EDFlqgKYPCbr09hJkbAI0ZDxTeImAaoSDvjg8ov33Y
EZuo0p0RNBB8wLTgrG2usuVuWu6t3jo1lmcaKg0T/ZZdtO4u7KnMcor6aUzfyzbW
qPiMj08L0B5PiJxVw/tqCWboFBqv1aSA3oJ7fnylsASQbEXwlbi2UY6MSj+ZmXzD
P/Mx/Im2K0/nyj4V27B6X80DPGFMkjkHueBUUSZYiMZnnM1LiMypLcuqQnCLnzzD
ME9/zCi8svp9pfivMOGxSG2gIyUfVKX87Kf4pMD14g9mZfeBNosW7/YDIQ==
-----END CERTIFICATE-----
JoycedeMacBook:HttpProxy joyce$ keytool -export -alias test -keystore local.jks -file local-publickey.cer
输入密钥库口令:
keytool 错误: java.lang.Exception: 别名 <test> 不存在
JoycedeMacBook:HttpProxy joyce$ keytool -export -localhost test -keystore local.jks -file local-publickey.cer
非法选项: -localhost
keytool -exportcert [OPTION]...

导出证书

选项:

-rfc 以 RFC 样式输出
-alias <alias> 要处理的条目的别名
-file <filename> 输出文件名
-keystore <keystore> 密钥库名称
-storepass <arg> 密钥库口令
-storetype <storetype> 密钥库类型
-providername <providername> 提供方名称
-providerclass <providerclass> 提供方类名
-providerarg <arg> 提供方参数
-providerpath <pathlist> 提供方类路径
-v 详细输出
-protected 通过受保护的机制的口令

使用 "keytool -help" 获取所有可用命令
JoycedeMacBook:HttpProxy joyce$ keytool -export -alias localhost -keystore local.jks -file local-publickey.cer
输入密钥库口令:
keytool 错误: java.lang.Exception: 别名 <localhost> 不存在
JoycedeMacBook:HttpProxy joyce$ keytool -export -keystore local.jks -file local-publickey.cer
输入密钥库口令:
存储在文件 <local-publickey.cer> 中的证书

 

safari 信任 chrom不信任

 

 

 

 

参考:

https://blog.csdn.net/xad707348125/article/details/47017851

https://www.freesion.com/article/48661311806/

 

 

 

 

4 连接泄漏

4.1 发现现象

4.1.1 家里网络mac

只能打开3-4个网页,之后便打不开了

4.1.2 公司网络win

maven密码加密,在maven使用代理时,发现过一段时间(当然比家里网络mac时间长的多)maven就阻塞了,不再发起请求

 

4.2 分析

猜测连接过度,浏览器虽然有6个连接限制,但代理服务器似乎不受控制,不同host不复用,最终代理服务器无法承载更多连接,也说明有连接泄漏

公司的maven代理执行时间长及其它内网网站未暴露这个问题,可能是因为maven及内网网站同host重复可用连接多,也可能是机器强,win连接能开的多

但是后来想一想并不完全是这样,maven代理有个特征每次都执行到某几个jar就阻塞了,而且重启服务器没用,这与家里重启服务就好了的现象不符合

 

 

 

4.3 验证

家里网络验证

netstat -an|grep 1999

发现连接逐渐变多,在比较少的时候,网页可以打开,比较多了之后,无法再打开,验证了猜想

 

4.4 修改

 

 

 

 

 

 

 

 

异常-连接泄漏 这个洞堵上后,正常(家里网络复核成功)

package com.jds.test.httpproxy.miniserver;

/**
 * Created by joyce on 2022/8/11.
 */
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.*;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;

//https://www.cnblogs.com/silyvin/articles/16573161.html
public class HttpServerJob implements Runnable {

    public static AtomicInteger open = new AtomicInteger(0);
    public static AtomicInteger close = new AtomicInteger(0);

    final EventLoopGroup CLIENT_BOSS_LOOP_GROUP = new NioEventLoopGroup(1);
    final EventLoopGroup CLIENT_WORKER_LOOP_GROUP = new NioEventLoopGroup(4);

    public static void main(String[] args) throws Exception {
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
        new Thread(new HttpServerJob(8990, "https://xxx/", "xxx")).start();
    }

    public HttpServerJob(int port, String url, String auth) {
        this.port = port;
        this.url = url;
        this.auth = auth;
    }

    private int port;
    private String url;
    private ReqQueue reqQueue = new ReqQueue();
    private String auth;

    @Override
    public void run() {
        startHttpServer();
    }

    public void startHttpServer() {
        try {
            ServerBootstrap bs = new ServerBootstrap();
            bs.group(CLIENT_BOSS_LOOP_GROUP, CLIENT_WORKER_LOOP_GROUP);
            bs.channel(NioServerSocketChannel.class);
            bs.childHandler(new HttpServerInitializer());

            ChannelFuture future = bs.bind(this.port).sync();

            System.out.println("http server start at " + port);
            reqQueue.startConsumer();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            CLIENT_BOSS_LOOP_GROUP.shutdownGracefully();
            CLIENT_WORKER_LOOP_GROUP.shutdownGracefully();
        }
    }

    private class HttpServerInitializer extends ChannelInitializer<SocketChannel> {

        @Override
        public void initChannel(SocketChannel ch) throws Exception {

            ChannelPipeline pipeline = ch.pipeline();

            pipeline.addLast(new HttpServerCodec());
            pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
            pipeline.addLast(new BodyToResponseEncoder());
            pipeline.addLast(new RequestToBodyDecoder());
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            super.exceptionCaught(ctx, cause);
        }
    }

    public static class ReqQueue {

        private BlockingQueue<OriginHttp> arrayBlockingQueue = new LinkedBlockingDeque<OriginHttp>(100);

        public void startConsumer() {
            for(int i=0; i<10; ++i) {
                new Thread(new Customer()).start();
            }
        }

        public void put(OriginHttp originHttp) {
            try {
                arrayBlockingQueue.put(originHttp);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private class Customer implements Runnable {

            @Override
            public void run() {
                try {
                    while (true){
                        OriginHttp originHttp = arrayBlockingQueue.take();
                        ProxySender.send(originHttp, originHttp.getUrl());
                        Thread.sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private class RequestToBodyDecoder extends MessageToMessageDecoder<FullHttpRequest> {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("active " + open.incrementAndGet());
            super.channelActive(ctx);
        }

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest, List<Object> list) throws Exception {

            HttpMethod httpMethod = fullHttpRequest.getMethod();
            String uri = fullHttpRequest.getUri();
            HttpHeaders httpHeaders = fullHttpRequest.headers();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, String> entry : httpHeaders.entries()) {
                map.put(entry.getKey(), entry.getValue());
            }

            ByteBuf msg = fullHttpRequest.content();
            byte[] bs = new byte[msg.readableBytes()];
            msg.readBytes(bs);

            OriginHttp originHttp = new OriginHttp(httpMethod.name(), uri, map, bs, channelHandlerContext);
            originHttp.setUrl(url);
            originHttp.setAuth(auth);
            reqQueue.put(originHttp);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.channel().close();
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("connect release " + close.incrementAndGet());
            super.channelInactive(ctx);
        }
    }

    public static class BodyToResponseEncoder extends MessageToMessageEncoder<ResHttp> {

        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, ResHttp resHttp, List<Object> list) throws Exception {

            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.valueOf(resHttp.getRet()),
                    Unpooled.wrappedBuffer(resHttp.getBody()));

            Map<String, String> headers = resHttp.getHeaders();
            for(Map.Entry<String, String> entry : headers.entrySet()) {
                response.headers().set(entry.getKey(), entry.getValue());

                if(entry.getKey().equals("Set-Cookie")) {
                    response.headers().set(entry.getKey(), entry.getValue().replace("Secure", "").replace("HttpOnly", ""));
                }
            }

            response.headers().remove("X-Frame-Options");
            response.headers().set("testproxy", "hhh");

            /**
             * one of content_length and chunked in response header neccesary in http long connections
             */
            if(!response.headers().contains("Transfer-Encoding") && !"chunked".equals(response.headers().get("Transfer-Encoding")))
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
//        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
//        response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaders.Names.CONTENT_TYPE);

            list.add(response);
        }
    }

    public static class OriginHttp {
        private String url;
        private String method;
        private String uri;
        private Map<String, String> headers;
        private byte [] body;
        ChannelHandlerContext context;
        private String auth;

        public OriginHttp(String method, String uri, Map<String, String> headers, byte [] body, ChannelHandlerContext context) {
            this.method = method;
            this.uri = uri;
            this.headers = headers;
            this.body = body;
            this.context = context;
        }

        public String getMethod() {
            return method;
        }

        public String getUri() {
            return uri;
        }

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

        public byte[] getBody() {
            return body;
        }

        public ChannelHandlerContext getContext() {
            return context;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getAuth() {
            return auth;
        }

        public void setAuth(String auth) {
            this.auth = auth;
        }
    }

    private static class ProxySender {

        public static void send(OriginHttp originHttp, String dir) {

            try {
                String method = originHttp.getMethod();
                String uri = originHttp.getUri();
                String url = new StringBuilder(dir).append(uri).toString();
                System.out.println(url);

                HttpUriRequest httpUriRequest = null;

                if ("GET".equals(method)) {
                    httpUriRequest = new HttpGet(url);
                } else if ("POST".equals(method)) {
                    httpUriRequest = new HttpPost(url);

                    HttpPost httpPost = (HttpPost) httpUriRequest;
                    try {
                        String st = new String(originHttp.getBody());
                        httpPost.setEntity(new StringEntity(st));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }

                Map<String, String> headers = originHttp.getHeaders();
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    String val = entry.getValue();
                    httpUriRequest.addHeader(entry.getKey(), val);
                }

    //        httpUriRequest.addHeader("Cookie", cookie);
                httpUriRequest.removeHeaders("Content-Length");
                httpUriRequest.removeHeaders("Origin");
                httpUriRequest.removeHeaders("Referer");
                httpUriRequest.removeHeaders("Host");
                httpUriRequest.addHeader("Origin", dir);
                httpUriRequest.addHeader("Referer", dir);
                httpUriRequest.addHeader("sm_user", originHttp.getAuth());
                //    httpUriRequest.addHeader("Host", dir);

                CloseableHttpClient httpClient = HttpClientFactory.createSSLClientDefault();
                org.apache.http.HttpResponse response = null;

                response = httpClient.execute(httpUriRequest);

                Map<String, String> map = new HashMap<>();
                Header[] headersRes = response.getAllHeaders();
                for (Header header : headersRes) {
                    map.put(header.getName(), header.getValue());
                }

                int ret = response.getStatusLine().getStatusCode();

                HttpEntity responseEntity = response.getEntity();
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                if(responseEntity != null) {
                    InputStream inputStream = responseEntity.getContent();
                    byte[] bytes = new byte[1024];
                    int i = 0;
                    while ((i = inputStream.read(bytes)) != -1) {
                        byteArrayOutputStream.write(bytes, 0, i);
                    }
                }

                ResHttp resHttp = new ResHttp(ret, map, byteArrayOutputStream.toByteArray());

                ChannelFuture channelFuture = originHttp.getContext().writeAndFlush(resHttp);
                channelFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {

                        /**
                         * to use http short connections by this line
                         */
                        future.channel().close();

                        if (!future.isSuccess()) {
                            future.cause().printStackTrace();
                            //future.channel().close();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                originHttp.context.channel().close();
            } finally {

            }
        }
    }

    private static class HttpClientFactory {

        public static CloseableHttpClient createSSLClientDefault() {
            try {
                SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        return true;
                    }
                }).build();

                HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);

                return HttpClients.custom().setSSLSocketFactory(sslsf).build();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return HttpClients.createDefault();
        }
    }

    private static class ResHttp {
        private int ret;
        private Map<String, String> headers;
        private byte [] body;

        public ResHttp(int ret, Map headers, byte [] body) {
            this.ret = ret;
            this.headers = headers;
            this.body = body;
        }

        public int getRet() {
            return ret;
        }

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

        public byte[] getBody() {
            return body;
        }
    }
}

 close为啥不放finally?因为write是异步的

package com.jds.test.httpproxy.miniserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;


import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.netty.handler.codec.http.*;
/**
 * Created by joyce on 2022/8/11.
 */
public class ProxyHttpsDecode {

    private static HttpServerJob.ReqQueue reqQueue = new HttpServerJob.ReqQueue();

    public static void start(final int port) throws Exception {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        try {
            serverBootstrap.channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .group(boss, worker)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            /**
                             * 不加不能直接访问
                             */
//                            SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
//                            sslEngine.setUseClientMode(false);
//                            pipeline.addLast("SslHandler", new SslHandler(sslEngine));
                            pipeline.addLast("HttpServerCodec", new HttpServerCodec());
                            pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(Integer.MAX_VALUE));
                            pipeline.addLast("BodyToResponseEncoder", new HttpServerJob.BodyToResponseEncoder());
                            pipeline.addLast("RequestToBodyDecoder", new RequestToBodyDecoder());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("http server start at " + port);
            reqQueue.startConsumer();
            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        start(1999);
    }

    private static class SSLContextFactory {

        public static SSLContext getSslContext() throws Exception {
            char[] passArray = "hsc123".toCharArray();
            SSLContext sslContext = SSLContext.getInstance("TLSv1");
            KeyStore ks = KeyStore.getInstance("JKS");
            FileInputStream inputStream = new FileInputStream("/Users/joyce/work/MyTest/HttpProxy/local.jks");
            ks.load(inputStream, passArray);
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, passArray);
            sslContext.init(kmf.getKeyManagers(), null, null);
            inputStream.close();
            return sslContext;

        }
    }

    private static class RequestToBodyDecoder extends MessageToMessageDecoder<FullHttpRequest> {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("active " + HttpServerJob.open.incrementAndGet());
            super.channelActive(ctx);
        }

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest, List<Object> list) throws Exception {

            if ("CONNECT".equalsIgnoreCase(fullHttpRequest.getMethod().name())) {//HTTPS建立代理握手
                HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                channelHandlerContext.writeAndFlush(response);
                System.out.println("ssl request");
                SSLEngine sslEngine = SSLContextFactory.getSslContext().createSSLEngine();
                sslEngine.setUseClientMode(false);
                channelHandlerContext.pipeline().addFirst("SslHandler", new SslHandler(sslEngine));
                return;
            }

            /**
             * 不能直接访问
             */
//            FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//            httpResponse.content().writeBytes("https".getBytes());
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");
//            httpResponse.headers().set(HttpHeaders.Names.CONTENT_LENGTH, httpResponse.content().readableBytes());
//            channelHandlerContext.writeAndFlush(httpResponse);
//            if(true)return;

            HttpMethod httpMethod = fullHttpRequest.getMethod();
            String uri = fullHttpRequest.getUri();
            HttpHeaders httpHeaders = fullHttpRequest.headers();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, String> entry : httpHeaders.entries()) {
                map.put(entry.getKey(), entry.getValue());
            }

            ByteBuf msg = fullHttpRequest.content();
            byte[] bs = new byte[msg.readableBytes()];
            msg.readBytes(bs);

            HttpServerJob.OriginHttp originHttp = new HttpServerJob.OriginHttp(httpMethod.name(), uri, map, bs, channelHandlerContext);

            String host = fullHttpRequest.headers().get("host");

            String url = "https://" + host + "/";
            String auth = "xxx";

            originHttp.setUrl(url);
            originHttp.setAuth(auth);
            reqQueue.put(originHttp);

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.channel().close();
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("connect release " + HttpServerJob.close.incrementAndGet());
            super.channelInactive(ctx);
        }
    }

}

 

 

 

5 应用

5.1 iframe

ifram三大困难:

跨域允许iframe,cookie,自签名ssl

第一个:

response.headers().remove("X-Frame-Options");

第二个:

用这种方式本质是建立一个同host不同端口的中间人,注意同host不同端口cookie是共享的 netty(二十三)mycas framework

第三个:

代理本身与母网站是同host,无论是CA还是自签名,浏览器都会放

 

5.2 demo 录像数据脱敏

 

5.3 鉴权 

server跑maven但不允许出现密码,那让server转发到个人机器,中间加上鉴权

 

5.4 跨域ajax

是否允许跨域

response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");

cookie,同iframe 及 with-creditial

自签名ssl,同iframe

 

posted on 2022-08-10 17:12  silyvin  阅读(222)  评论(0编辑  收藏  举报