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为什么能篡改报文?
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
浙公网安备 33010602011771号