源码改造, 反向代理支持加解密、非加密传输

This commit is contained in:
ztzh_xieyun 2024-04-25 13:54:14 +08:00
parent 87f61bc4d8
commit 99fcb47f55
37 changed files with 2491 additions and 663 deletions

View File

@ -96,10 +96,6 @@
<groupId>io.vertx</groupId> <groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId> <artifactId>vertx-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-http-proxy</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.vertx</groupId> <groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId> <artifactId>vertx-web</artifactId>

View File

@ -77,6 +77,7 @@ public class BodyHandlerImpl implements BodyHandler {
@Override @Override
public void handle(RoutingContext context) { public void handle(RoutingContext context) {
// TODO 改造了这个地方
final HttpServerRequest request = context.request(); final HttpServerRequest request = context.request();
final HttpServerResponse response = context.response(); final HttpServerResponse response = context.response();
String sacAppHeaderKey = request.getHeader(DynamicBuildServer.SAC_APP_HEADER_KEY); String sacAppHeaderKey = request.getHeader(DynamicBuildServer.SAC_APP_HEADER_KEY);

View File

@ -1,25 +1,12 @@
package com.sf.vertx.handle; package com.sf.vertx.handle;
import com.sf.vertx.security.MainSecurity;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.PoolOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClient;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.HttpProxy; import io.vertx.httpproxy.HttpProxy;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import lombok.extern.slf4j.Slf4j;
/** /**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a> * @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/ */
@Slf4j
public class ProxyHandlerImpl implements ProxyHandler { public class ProxyHandlerImpl implements ProxyHandler {
private final HttpProxy httpProxy; private final HttpProxy httpProxy;
@ -40,23 +27,9 @@ public class ProxyHandlerImpl implements ProxyHandler {
@Override @Override
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
// 创建一个响应并设置参数 // TODO 改造了这个地方
// ctx.request().response().setStatusCode(200) httpProxy.handle(ctx, mainWebClient);
// .putHeader("content-type", "text/plain").end(body);
// 发起一个请求
// this.mainWebClient.post(9198, "127.0.0.1", "/vertx/body").sendJson(ctx.getBodyAsString(), h -> {
// if (h.succeeded()) {
// JsonObject responseData = h.result().bodyAsJsonObject();
// log.info("responseData:{}", responseData);
// // 加密
// String dataStr = MainSecurity.aesEncrypt(responseData.toString(), "dadddsdfadfadsfa33323223");
// log.info("dataStr:{}", dataStr);
// ctx.request().response().setStatusCode(200).putHeader("content-type", "application/json").end(dataStr);
// }
// });
// 原始代码只有如下一句 // 原始代码只有如下一句
httpProxy.handle(ctx.request()); // httpProxy.handle(ctx.request());
} }
} }

View File

@ -119,8 +119,9 @@ public class DynamicBuildServer implements ApplicationRunner {
return context.sendRequest(); return context.sendRequest();
} }
}); });
// mainHttpRouter.route().handler(ProxyHandler.create(proxy));
WebClient mainWebClient = WebClient.create(VERTX); WebClient mainWebClient = WebClient.create(VERTX);
//mainHttpRouter.route().handler(ProxyHandler.create(proxy));
mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy)); mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy));
} }
@ -136,6 +137,7 @@ public class DynamicBuildServer implements ApplicationRunner {
SacLoadBalancing sacLoadBalancing = null; SacLoadBalancing sacLoadBalancing = null;
// 2是否存在server服务配置 // 2是否存在server服务配置
if (appConfig.getService() != null && appConfig.getService().size() > 0) { if (appConfig.getService() != null && appConfig.getService().size() > 0) {
// header传递服务名, 这样就不需要遍历,提高性能
for (SacService service : appConfig.getService()) { for (SacService service : appConfig.getService()) {
// uri是否匹配 // uri是否匹配
boolean match = false; boolean match = false;

View File

@ -1,84 +0,0 @@
package com.sf.vertx.strategy.security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* 本类采用对称加密 加密算法{@link #PADDINIG_MODE}
* {@link #RESULT_TYPE} 密文结果1=base64 2=hex
*/
public class AesUtils {
/**
* 整平方法
*/
private static final int RESULT_TYPE = 2;
/**
* 加密方法
*/
private static final String AES_ALGORITHM = "AES";
/**
* 填充方法
*/
private static final String PADDINIG_MODE = "AES/CBC/PKCS5Padding";
/**
* 偏移量
*/
private static final byte[] IV = "0000000000000000".getBytes();
public static String encrypt(String s, String k) throws Exception {
SecretKeySpec key = new SecretKeySpec(StringUtils.getBytes(k), AES_ALGORITHM);
byte[] data = encrypt(StringUtils.getBytes(s), key);
String result;
switch (RESULT_TYPE) {
case 1:
result = Base64Utils.encode(data);
break;
case 2:
result = HexUtils.bytes2Hex(data);
break;
default:
throw new Exception("Unsupport Result Type");
}
return result;
}
public static String decrypt(String s, String k) throws Exception {
SecretKeySpec key = new SecretKeySpec(StringUtils.getBytes(k), AES_ALGORITHM);
byte[] data;
switch (RESULT_TYPE) {
case 1:
data = Base64Utils.decode(s);
break;
case 2:
data = HexUtils.hex2Bytes(s);
break;
default:
throw new Exception("Unsupport Result Type");
}
return StringUtils.bytes2String(decrypt(data, key));
}
private static byte[] encrypt(byte[] data, SecretKeySpec keySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
IvParameterSpec ivspec = new IvParameterSpec(IV);
Cipher cipher = Cipher.getInstance(PADDINIG_MODE);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
return cipher.doFinal(data);
}
private static byte[] decrypt(byte[] data, SecretKeySpec keySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
IvParameterSpec ivspec = new IvParameterSpec(IV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec);
return cipher.doFinal(data);
}
}

View File

@ -1,31 +0,0 @@
package com.sf.vertx.strategy.security;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
/**
* 将Base64字符串和字节数组互转
*/
public class Base64Utils {
/**
* 将字节数组编码为base64字符串
*
* @param b 字节数组
* @return String 字符串
*/
public static String encode(byte[] b) {
return Base64.getEncoder().encodeToString(b);
}
/**
* 将字符串转base64为字节数组
*
* @param s 字符串
* @return byte[] 字节数组
*/
public static byte[] decode(String s) {
return Base64.getDecoder().decode(s);
}
}

View File

@ -1,53 +0,0 @@
package com.sf.vertx.strategy.security;
/**
* 将字节数组和HEX字符串进行互转
*/
public class HexUtils {
private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* 字节数组转字符串
*
* @param byteArr 字节数组
* @return 字符串
*/
public static String bytes2Hex(byte[] byteArr) {
StringBuilder sb = new StringBuilder(byteArr.length);
for (byte b : byteArr) {
String sTemp = Integer.toHexString(255 & b);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
/**
* HEX字符串转字节数组
*
* @param str 字符串
* @return 字节数组
*/
public static byte[] hex2Bytes(String str) {
if (str == null) {
return null;
} else {
char[] hex = str.toCharArray();
int length = hex.length / 2;
byte[] raw = new byte[length];
for (int i = 0; i < length; ++i) {
int high = Character.digit(hex[i * 2], 16);
int low = Character.digit(hex[i * 2 + 1], 16);
int value = high << 4 | low;
if (value > 127) {
value -= 256;
}
raw[i] = (byte) value;
}
return raw;
}
}
}

View File

@ -1,78 +0,0 @@
package com.sf.vertx.strategy.security;
import java.util.logging.Logger;
/**
* 本项目中真正对外提供服务的工具类
*/
public class MainSecurity {
private static final Logger LOGGER = Logger.getLogger(MainSecurity.class.getName());
/**
* 加密当失败的时候返回空字符串
*
* @param content
* @param pubKey
* @return
*/
public static String rsaEncrypt(String content, String pubKey) {
try {
return RSA2Utils.encrypt(content, pubKey);
} catch (Exception e) {
LOGGER.info("RSA加密失败");
e.printStackTrace();
return null;
}
}
public static String rsaDecrypt(String content, String priKey) {
try {
return RSA2Utils.decrypt(content, priKey);
} catch (Exception e) {
LOGGER.info("RSA解密失败");
e.printStackTrace();
return null;
}
}
public static String aesEncrypt(String content, String key) {
try {
return AesUtils.encrypt(content, key);
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("AES加密失败");
return null;
}
}
public static String aesDecrypt(String content, String key) {
try {
return AesUtils.decrypt(content, key);
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("AES解密失败");
return null;
}
}
public static String sign(String content) {
try {
return MessageDigest.md5(content);
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("MD5加密失败");
return null;
}
}
public static void main(String[] args) {
System.out.println(aesEncrypt("{\n"
+ " \"errorCode\": \"test\",\n"
+ " \"result\": 0,\n"
+ " \"data\": {\n"
+ " \"username\" : \"test\"\n"
+ " }\n"
+ "}", "dadddsdfadfadsfa33323223"));
System.out.println(aesDecrypt("59A69B6BBCF046C3CF9953C5CC078CC68ABBED261030F3C119F65B9A3EB306423F32560751B22616500070BCE57153BE2B6E47CAC380C7D226862558256325826FECC43E957FCACB74E22BA48125FABC01FC77B127E384F8F62D9881741931A8C1ABF0A391F06606A060499EB53EF217", "dadddsdfadfadsfa33323223"));
}
}

View File

@ -1,47 +0,0 @@
package com.sf.vertx.strategy.security;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
public class MessageDigest {
public static String md5(String text) throws NoSuchAlgorithmException {
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("md5");
byte[] buffer = digest.digest(text.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : buffer) {
int a = b & 0xff;
String hex = Integer.toHexString(a);
if (hex.length() == 1) {
hex = 0 + hex;
}
sb.append(hex);
}
return sb.toString();
}
private static String hmacSha1(String data, String key, int type) throws Exception {
//根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKeySpec signinKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
//生成一个指定 Mac 算法 Mac 对象
Mac mac = Mac.getInstance("HmacSHA1");
//用给定密钥初始化 Mac 对象
mac.init(signinKey);
//完成 Mac 操作
byte[] rawHmac = mac.doFinal(StringUtils.getBytes(data));
String result;
switch (type) {
case 1:
result = Base64Utils.encode(rawHmac);
break;
case 2:
result = HexUtils.bytes2Hex(rawHmac);
break;
default:
throw new Exception("Unsupport Type");
}
return result;
}
}

View File

@ -1,282 +0,0 @@
package com.sf.vertx.strategy.security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
/**
* 本类进行非对称加密不推荐使用非对称加密对长字符串进行加密或者解密徒增资源消耗另外由于长度限制过长的字符串的加密和解密会使用循环对数据分段加密本类采用的
* 密钥字符串均为Base64加密后的
* 另外所有异常都会抛出
* 下面将会列举几个可以自定义或者暴露出去的接口和参数
* {@link #IS_LONG_TEXT} 是否否对长文本处理
* {@link #RESULT_TYPE} 密文结果1=base64 2=hex
* {@link #RSA_ALGORITHM} RSA算法
* {@link #encrypt(String, String)} 加密方法
* {@link #decrypt(String, String)} 解密方法
* {@link #getKeyPair} 解密方法
*/
public class RSA2Utils {
/**
* 是否对长文本加密请参照{@link #MAX_DECRYPT_BLOCK}{@link #MAX_ENCRYPT_BLOCK}
*/
private static final boolean IS_LONG_TEXT = true;
/**
* 结果类型
*/
private static final int RESULT_TYPE = 2;
/**
* RSA 算法
*/
private static final String RSA_ALGORITHM = "RSA";
/**
* 长文本解密块大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 长文本加密块大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* KyeSize
*/
private static final int KEY_SIZE = 2048;
/**
* 加密
*
* @param content 待加密的字符串
* @param pubKey 公钥字符串
* @return 加密后的文本
* @throws Exception 异常
*/
public static String encrypt(String content, String pubKey) throws Exception {
byte[] data = StringUtils.getBytes(content);
PublicKey publicKey = string2PubKey(pubKey);
byte[] resultArr;
if (IS_LONG_TEXT) {
resultArr = encryptLongStr(data, publicKey);
} else {
resultArr = encrypt(data, publicKey);
}
String result;
switch (RESULT_TYPE) {
case 1:
result = Base64Utils.encode(resultArr);
break;
case 2:
result = HexUtils.bytes2Hex(resultArr);
break;
default:
throw new Exception("Unsupport result type");
}
return result;
}
/**
* @param content 密文内容
* @param priKey 私钥
* @return 解密后的字符串
* @throws Exception 异常
*/
public static String decrypt(String content, String priKey) throws Exception {
byte[] data;
switch (RESULT_TYPE) {
case 1:
data = Base64Utils.decode(content);
break;
case 2:
data = HexUtils.hex2Bytes(content);
break;
default:
throw new Exception("Unsupport result type");
}
PrivateKey privateKey = string2PrivateKey(priKey);
byte[] result;
if (IS_LONG_TEXT) {
result = decryptLongStr(data, privateKey);
} else {
result = decrypt(privateKey, data);
}
return StringUtils.bytes2String(result);
}
/**
* 响应公私钥对
*
* @return 0号 公钥 1号 私钥
* @throws NoSuchAlgorithmException 异常
*/
public static List<String> getKeyPair() throws NoSuchAlgorithmException {
KeyPair keyPairObj = getKeyPairObj();
List<String> data = new ArrayList<>();
data.add(Base64Utils.encode(keyPairObj.getPublic().getEncoded()));
data.add(Base64Utils.encode(keyPairObj.getPrivate().getEncoded()));
return data;
// jdk 17
//return List.of(Base64Utils.encode(keyPairObj.getPublic().getEncoded()), Base64Utils.encode(keyPairObj.getPrivate().getEncoded()));
}
/**
* 将公钥字符串转化为对象
*
* @param s base64字符串
* @return 公钥
* @throws NoSuchAlgorithmException 异常
* @throws UnsupportedEncodingException 异常
* @throws InvalidKeySpecException 异常
*/
private static PublicKey string2PubKey(String s) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(s));
return keyFactory.generatePublic(keySpec);
}
/**
* 对段字符串进行加密
*
* @param bytes 字节数组
* @param publicKey 公钥
* @return 加密后的数组
* @throws InvalidKeyException 异常
* @throws BadPaddingException 异常
* @throws IllegalBlockSizeException 异常
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
*/
private static byte[] encrypt(byte[] bytes, PublicKey publicKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(bytes);
}
/**
* 对长字符串进行加密
*
* @param bytes 字节数组
* @param publicKey 公钥
* @return 加密后的数组
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
* @throws InvalidKeyException 异常
*/
private static byte[] encryptLongStr(byte[] bytes, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = bytes.length;
byte[] encryptedData = new byte[0];
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(bytes, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(bytes, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
encryptedData = out.toByteArray();
} catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
return encryptedData;
}
/**
* 私钥字符串转为私钥对象
*
* @param priStr 私钥字符串
* @return 私钥对象
* @throws NoSuchAlgorithmException 异常
* @throws InvalidKeySpecException 异常
*/
private static PrivateKey string2PrivateKey(String priStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Utils.decode(priStr));
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
/**
* 解密
*
* @param privateKey 私钥
* @param bytes 字节数组
* @return 解密后的字节数组
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
* @throws BadPaddingException 异常
* @throws IllegalBlockSizeException 异常
* @throws InvalidKeyException 异常
*/
public static byte[] decrypt(PrivateKey privateKey, byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(bytes);
}
/**
* 解密
*
* @param data 解密前的字节数组
* @param privateKey 私钥
* @return 解密后的字节数组
* @throws InvalidKeyException 异常
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
*/
public static byte[] decryptLongStr(byte[] data, PrivateKey privateKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int inputLen = data.length;
byte[] result = new byte[0];
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
result = out.toByteArray();
} catch (BadPaddingException | IllegalBlockSizeException | IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 获得一堆公私钥
*
* @return KeyPair对象
* @throws NoSuchAlgorithmException 异常
*/
private static KeyPair getKeyPairObj() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
SecureRandom secureRandom = new SecureRandom(StringUtils.getBytes(String.valueOf(System.currentTimeMillis())));
keyPairGenerator.initialize(KEY_SIZE, secureRandom);
return keyPairGenerator.genKeyPair();
}
}

View File

@ -1,53 +0,0 @@
package com.sf.vertx.strategy.security;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class StringUtils {
/**
* 编码方法
*/
private static final Charset CHARSET = StandardCharsets.UTF_8;
/**
* 根据本类的常量对字符串进行获取字节数组的操作
*
* @param s String:待处理的字符串
* @return byte[] 形成的字节数组
*/
public static byte[] getBytes(String s) {
return s.getBytes(CHARSET);
}
/**
* 将字节数组转为文本
*
* @param data 字节数组
* @return 文本
*/
public static String bytes2String(byte[] data) {
return new String(data, CHARSET);
}
/**
* 字符串是不是空的
*
* @param str 字符串
* @return 结果
*/
public static boolean isBlank(String str) {
int strLen;
if (str != null && (strLen = str.length()) != 0) {
for (int i = 0; i < strLen; ++i) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
} else {
return true;
}
}
}

View File

@ -0,0 +1,182 @@
package examples;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.SocketAddress;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.HttpProxy;
import io.vertx.httpproxy.ProxyContext;
import io.vertx.httpproxy.ProxyInterceptor;
import io.vertx.httpproxy.ProxyOptions;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import io.vertx.httpproxy.cache.CacheOptions;
/**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/
public class HttpProxyExamples {
public void origin(Vertx vertx) {
HttpServer originServer = vertx.createHttpServer();
originServer.requestHandler(req -> {
req.response()
.putHeader("content-type", "text/html")
.end("<html><body><h1>I'm the target resource!</h1></body></html>");
}).listen(7070);
}
public void proxy(Vertx vertx) {
HttpClient proxyClient = vertx.createHttpClient();
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
proxy.origin(7070, "origin");
HttpServer proxyServer = vertx.createHttpServer();
proxyServer.requestHandler(proxy).listen(8080);
}
private SocketAddress resolveOriginAddress(HttpServerRequest request) {
return null;
}
public void originSelector(HttpProxy proxy) {
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(request)));
}
private RequestOptions resolveOriginOptions(HttpServerRequest request) {
return null;
}
public void originRequestProvider(HttpProxy proxy) {
proxy.originRequestProvider((request, client) -> client.request(resolveOriginOptions(request)));
}
public void inboundInterceptor(HttpProxy proxy) {
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
filter(proxyRequest.headers());
// Continue the interception chain
return context.sendRequest();
}
});
}
public void outboundInterceptor(HttpProxy proxy) {
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<Void> handleProxyResponse(ProxyContext context) {
ProxyResponse proxyResponse = context.response();
filter(proxyResponse.headers());
// Continue the interception chain
return context.sendResponse();
}
});
}
public void bodyFilter(HttpProxy proxy) {
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<Void> handleProxyResponse(ProxyContext context) {
ProxyResponse proxyResponse = context.response();
// Create a filtered body
Body filteredBody = filter(proxyResponse.getBody());
// And then let the response use it
proxyResponse.setBody(filteredBody);
// Continue the interception chain
return context.sendResponse();
}
});
}
public void immediateResponse(HttpProxy proxy) {
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
// Release the underlying resources
proxyRequest.release();
// Create a response and populate it
ProxyResponse proxyResponse = proxyRequest.response()
.setStatusCode(200)
.putHeader("content-type", "text/plain")
.setBody(Body.body(Buffer.buffer("Hello World")));
return Future.succeededFuture(proxyResponse);
}
});
}
private void filter(MultiMap headers) {
//
}
private Body filter(Body body) {
return body;
}
public void more(Vertx vertx, HttpClient proxyClient) {
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient).originSelector(
address -> Future.succeededFuture(SocketAddress.inetSocketAddress(7070, "origin"))
);
}
public void lowLevel(Vertx vertx, HttpServer proxyServer, HttpClient proxyClient) {
proxyServer.requestHandler(request -> {
ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request);
proxyClient.request(proxyRequest.getMethod(), 8080, "origin", proxyRequest.getURI())
.compose(proxyRequest::send)
.onSuccess(proxyResponse -> {
// Send the proxy response
proxyResponse.send();
})
.onFailure(err -> {
// Release the request
proxyRequest.release();
// Send error
request.response().setStatusCode(500)
.send();
});
});
}
public void overrideAuthority(HttpProxy proxy) {
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
proxyRequest.setAuthority(HostAndPort.create("example.com", 80));
return ProxyInterceptor.super.handleProxyRequest(context);
}
});
}
public void cacheConfig(Vertx vertx, HttpClient proxyClient) {
HttpProxy proxy = HttpProxy.reverseProxy(new ProxyOptions().setCacheOptions(new CacheOptions()), proxyClient);
}
}

View File

@ -0,0 +1,6 @@
/**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/
@Source
package examples;
import io.vertx.docgen.Source;

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
import io.vertx.httpproxy.impl.BufferedReadStream;
/**
* Handles the HTTP proxy body.
* <p>
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@VertxGen
public interface Body {
/**
* Create a new {@code Body} instance.
*
* @param stream the {@code ReadStream} of the body
* @param len the determined length of the body
* @return a reference to this, so the API can be used fluently
*/
static Body body(ReadStream<Buffer> stream, long len) {
return new Body() {
@Override
public long length() {
return len;
}
@Override
public ReadStream<Buffer> stream() {
return stream;
}
};
}
/**
* Create a new {@code Body} instance.
*
* @param stream the {@link ReadStream} of the body
* @return a reference to this, so the API can be used fluently
*/
static Body body(ReadStream<Buffer> stream) {
return body(stream, -1L);
}
/**
* Create a new {@code Body} instance.
*
* @param buffer the {@link Buffer} of the body
* @return a reference to this, so the API can be used fluently
*/
static Body body(Buffer buffer) {
return new Body() {
@Override
public long length() {
return buffer.length();
}
@Override
public ReadStream<Buffer> stream() {
return new BufferedReadStream(buffer);
}
};
}
/**
*
* Get length of the {@code Body}.
*
* @return the body length or {@code -1} if that can't be determined
*/
long length();
/**
*
* Get stream of the {@code Body}.
*
* @return the body stream
*/
ReadStream<Buffer> stream();
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import io.vertx.httpproxy.impl.ReverseProxy;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Handles the HTTP reverse proxy logic between the <i><b>user agent</b></i> and the <i><b>origin</b></i>.
* <p>
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@VertxGen
public interface HttpProxy extends Handler<HttpServerRequest> {
/**
* Create a new {@code HttpProxy} instance.
*
* @param client the {@code HttpClient} that forwards <i><b>outbound</b></i> requests to the <i><b>origin</b></i>.
* @return a reference to this, so the API can be used fluently.
*/
static HttpProxy reverseProxy(HttpClient client) {
return new ReverseProxy(new ProxyOptions(), client);
}
/**
* Create a new {@code HttpProxy} instance.
*
* @param client the {@code HttpClient} that forwards <i><b>outbound</b></i> requests to the <i><b>origin</b></i>.
* @return a reference to this, so the API can be used fluently.
*/
static HttpProxy reverseProxy(ProxyOptions options, HttpClient client) {
return new ReverseProxy(options, client);
}
/**
* Set the {@code SocketAddress} of the <i><b>origin</b></i>.
*
* @param address the {@code SocketAddress} of the <i><b>origin</b></i>
* @return a reference to this, so the API can be used fluently
*/
@Fluent
default HttpProxy origin(SocketAddress address) {
return originSelector(req -> Future.succeededFuture(address));
}
/**
* Set the host name and port number of the <i><b>origin</b></i>.
*
* @param port the port number of the <i><b>origin</b></i> server
* @param host the host name of the <i><b>origin</b></i> server
* @return a reference to this, so the API can be used fluently
*/
@Fluent
default HttpProxy origin(int port, String host) {
return origin(SocketAddress.inetSocketAddress(port, host));
}
/**
* Set a selector that resolves the <i><b>origin</b></i> address based on the incoming HTTP request.
*
* @param selector the selector
* @return a reference to this, so the API can be used fluently
*/
@Fluent
default HttpProxy originSelector(Function<HttpServerRequest, Future<SocketAddress>> selector) {
return originRequestProvider((req, client) -> selector
.apply(req)
.flatMap(server -> client.request(new RequestOptions().setServer(server))));
}
/**
* Set a provider that creates the request to the <i><b>origin</b></i> server based the incoming HTTP request.
* Setting a provider overrides any origin selector previously set.
*
* @param provider the provider
* @return a reference to this, so the API can be used fluently
*/
@GenIgnore()
@Fluent
HttpProxy originRequestProvider(BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> provider);
/**
* Add an interceptor to the interceptor chain.
*
* @param interceptor
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpProxy addInterceptor(ProxyInterceptor interceptor);
/**
* Handle the <i><b>outbound</b></i> {@code HttpServerRequest}.
*
* @param request the outbound {@code HttpServerRequest}
*/
void handle(HttpServerRequest request);
void handle(RoutingContext ctx, WebClient mainWebClient);
}

View File

@ -0,0 +1,48 @@
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
/**
* A controller for proxy interception.
*/
@VertxGen
public interface ProxyContext {
/**
* @return the proxy request
*/
ProxyRequest request();
/**
* @return the proxy response, it might be {@code null} if the response has not been sent
*/
ProxyResponse response();
/**
*
*/
Future<ProxyResponse> sendRequest();
/**
*
*/
Future<Void> sendResponse();
/**
* Attach a payload to the context
*
* @param name the payload name
* @param value any payload value
*/
void set(String name, Object value);
/**
* Get a payload attached to this context
*
* @param name the payload name
* @param type the expected payload type
* @return the attached payload
*/
<T> T get(String name, Class<T> type);
}

View File

@ -0,0 +1,31 @@
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
/**
* A {@link HttpProxy} interceptor.
*/
@VertxGen(concrete = false)
public interface ProxyInterceptor {
/**
* Handle the proxy request at the stage of this interceptor.
*
* @param context the proxy context
* @return when the request has actually been sent to the origin
*/
default Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
return context.sendRequest();
}
/**
* Handle the proxy response at the stage of this interceptor.
*
* @param context the proxy context
* @return when the response has actually been sent to the user-agent
*/
default Future<Void> handleProxyResponse(ProxyContext context) {
return context.sendResponse();
}
}

View File

@ -0,0 +1,79 @@
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.json.annotations.JsonGen;
import io.vertx.core.json.JsonObject;
import io.vertx.httpproxy.cache.CacheOptions;
/**
* Proxy options.
*/
@DataObject
@JsonGen(publicConverter = false)
public class ProxyOptions {
/**
* Enable WebSocket support : {@code true}
*/
public static final boolean DEFAULT_SUPPORT_WEBSOCKET = true;
private CacheOptions cacheOptions;
private boolean supportWebSocket;
public ProxyOptions(JsonObject json) {
ProxyOptionsConverter.fromJson(json, this);
}
public ProxyOptions() {
supportWebSocket = DEFAULT_SUPPORT_WEBSOCKET;
}
/**
* @return the cache options
*/
public CacheOptions getCacheOptions() {
return cacheOptions;
}
/**
* Set the cache options that configures the proxy.
*
* {@code null} cache options disables caching, by default cache is disabled.
*
* @param cacheOptions the cache options
* @return a reference to this, so the API can be used fluently
*/
public ProxyOptions setCacheOptions(CacheOptions cacheOptions) {
this.cacheOptions = cacheOptions;
return this;
}
/**
* @return whether WebSocket are supported
*/
public boolean getSupportWebSocket() {
return supportWebSocket;
}
/**
* Set whether WebSocket are supported.
*
* @param supportWebSocket {@code true} to enable WebSocket support, {@code false} otherwise
* @return a reference to this, so the API can be used fluently
*/
public ProxyOptions setSupportWebSocket(boolean supportWebSocket) {
this.supportWebSocket = supportWebSocket;
return this;
}
@Override
public String toString() {
return toJson().toString();
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
ProxyOptionsConverter.toJson(this, json);
return json;
}
}

View File

@ -0,0 +1,47 @@
package io.vertx.httpproxy;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.impl.JsonUtil;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* Converter and mapper for {@link io.vertx.httpproxy.ProxyOptions}.
* NOTE: This class has been automatically generated from the {@link io.vertx.httpproxy.ProxyOptions} original class using Vert.x codegen.
*/
public class ProxyOptionsConverter {
private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, ProxyOptions obj) {
for (java.util.Map.Entry<String, Object> member : json) {
switch (member.getKey()) {
case "cacheOptions":
if (member.getValue() instanceof JsonObject) {
obj.setCacheOptions(new io.vertx.httpproxy.cache.CacheOptions((io.vertx.core.json.JsonObject)member.getValue()));
}
break;
case "supportWebSocket":
if (member.getValue() instanceof Boolean) {
obj.setSupportWebSocket((Boolean)member.getValue());
}
break;
}
}
}
static void toJson(ProxyOptions obj, JsonObject json) {
toJson(obj, json.getMap());
}
static void toJson(ProxyOptions obj, java.util.Map<String, Object> json) {
if (obj.getCacheOptions() != null) {
json.put("cacheOptions", obj.getCacheOptions().toJson());
}
json.put("supportWebSocket", obj.getSupportWebSocket());
}
}

View File

@ -0,0 +1,181 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.HostAndPort;
import io.vertx.httpproxy.impl.ProxiedRequest;
/**
*
* Handles the interoperability of the <b>request</b> between the <i><b>user agent</b></i> and the <i><b>origin</b></i>.
*
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@VertxGen
public interface ProxyRequest {
/**
* Create a new {@code ProxyRequest} instance, the proxied request will be paused.
*
* @param proxiedRequest the {@code HttpServerRequest} that is proxied
* @return a reference to this, so the API can be used fluently
*/
static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) {
proxiedRequest.pause();
return new ProxiedRequest(proxiedRequest);
}
/**
* @return the HTTP version of the proxied request
*/
HttpVersion version();
/**
* @return the absolute URI of the proxied request
*/
String absoluteURI();
/**
* @return the HTTP method to be sent to the <i><b>origin</b></i> server.
*/
HttpMethod getMethod();
/**
* Set the HTTP method to be sent to the <i><b>origin</b></i> server.
*
* <p>The initial HTTP method value is the proxied request HTTP method.
*
* @param method the new HTTP method
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest setMethod(HttpMethod method);
/**
* @return the request URI to be sent to the <i><b>origin</b></i> server.
*/
String getURI();
/**
* Set the request URI to be sent to the <i><b>origin</b></i> server.
*
* <p>The initial request URI value is the proxied request URI.
*
* @param uri the new URI
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest setURI(String uri);
/**
* @return the request body to be sent to the <i><b>origin</b></i> server.
*/
Body getBody();
/**
* Set the request body to be sent to the <i><b>origin</b></i> server.
*
* <p>The initial request body value is the proxied request body.
*
* @param body the new body
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest setBody(Body body);
/**
* Set the request authority
*
* <ul>
* <li>for HTTP/1 the {@literal Host} header</li>
* <li>for HTTP/2 the {@literal :authority} pseudo header</li>
* </ul>
*
* The value must follow the {@literal <host>:<port>} syntax.
*
* @param authority the authority
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest setAuthority(HostAndPort authority);
/**
* @return the request authority, for HTTP2 the {@literal :authority} pseudo header otherwise the {@literal Host} header
*/
HostAndPort getAuthority();
/**
* @return the headers that will be sent to the origin server, the returned headers can be modified. The headers
* map is populated with the proxied request headers
*/
MultiMap headers();
/**
* Put an HTTP header
*
* @param name The header name
* @param value The header value
* @return a reference to this, so the API can be used fluently
*/
@GenIgnore
@Fluent
ProxyRequest putHeader(CharSequence name, CharSequence value);
/**
* Proxy this request to the <i><b>origin</b></i> server using the specified {@code request} and then send the proxy response.
*
* @param request the request connected to the <i><b>origin</b></i> server
*/
default Future<Void> proxy(HttpClientRequest request) {
return send(request).flatMap(resp -> resp.send());
}
/**
* Send this request to the <i><b>origin</b></i> server using the specified {@code request}.
*
* <p> The returned future will be completed with the proxy response returned by the <i><b>origin</b></i>.
*
* @param request the request connected to the <i><b>origin</b></i> server
*/
Future<ProxyResponse> send(HttpClientRequest request);
/**
* Release the proxy request and its associated resources
*
* <p> The HTTP server request is resumed, no HTTP server response is sent.
*
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest release();
/**
* @return the proxied HTTP server request
*/
HttpServerRequest proxiedRequest();
/**
* Create and return the proxy response.
*
* @return the proxy response
*/
ProxyResponse response();
}

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
import java.util.function.Function;
/**
*
* Handles the interoperability of the <b>response</b> between the <i><b>origin</b></i> and the <i><b>user agent</b></i>.
*
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@VertxGen
public interface ProxyResponse {
/**
*
* Return the corresponding {@code ProxyRequest}.
*
* @return the proxy request
*/
ProxyRequest request();
/**
* Get the status code.
*
* @return the status code to be sent to the <i><b>user agent</b></i>
*/
int getStatusCode();
/**
* Set the status code to be sent to the <i><b>user agent</b></i>.
*
* <p> The initial value is the proxied response status code.
*
* @param sc the status code
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyResponse setStatusCode(int sc);
/**
* Get the status message.
*
* @return the status message to be sent to the <i><b>user agent</b></i>
*/
String getStatusMessage();
/**
* Set the status message to be sent to the <i><b>user agent</b></i>.
*
* <p> The initial value is the proxied response status message.
*
* @param statusMessage the status message
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyResponse setStatusMessage(String statusMessage);
/**
* @return the headers that will be sent to the <i><b>user agent</b></i>, the returned headers can be modified. The headers
* map is populated with the proxied response headers
*/
MultiMap headers();
/**
* Put an HTTP header.
*
* @param name The header name
* @param value The header value
* @return a reference to this, so the API can be used fluently
*/
@GenIgnore
@Fluent
ProxyResponse putHeader(CharSequence name, CharSequence value);
/**
* Get the body of the response.
*
* @return the response body to be sent to the <i><b>user agent</b></i>
*/
Body getBody();
/**
* Set the request body to be sent to the <i><b>user agent</b></i>.
*
* <p>The initial request body value is the proxied response body.
*
* @param body the new body
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyResponse setBody(Body body);
boolean publicCacheControl();
long maxAge();
/**
* @return the {@code etag} sent by the <i><b>origin</b></i> response
*/
String etag();
/**
* Send the proxies response to the <i><b>user agent</b></i>.
*/
Future<Void> send();
/**
* Release the proxy response.
*
* <p> The proxied response is resumed, no HTTP response is sent to the <i><b>user-agent</b></i>
*/
@Fluent
ProxyResponse release();
}

View File

@ -0,0 +1,61 @@
package io.vertx.httpproxy.cache;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.json.annotations.JsonGen;
import io.vertx.core.impl.Arguments;
import io.vertx.core.json.JsonObject;
import io.vertx.httpproxy.impl.CacheImpl;
import io.vertx.httpproxy.spi.cache.Cache;
/**
* Cache options.
*/
@DataObject
@JsonGen(publicConverter = false)
public class CacheOptions {
public static final int DEFAULT_MAX_SIZE = 1000;
private int maxSize = DEFAULT_MAX_SIZE;
public CacheOptions() {
}
public CacheOptions(JsonObject json) {
CacheOptionsConverter.fromJson(json, this);
}
/**
* @return the max number of entries the cache can hold
*/
public int getMaxSize() {
return maxSize;
}
/**
* Set the max number of entries the cache can hold.
*
* @param maxSize the max size
* @return a reference to this, so the API can be used fluently
*/
public CacheOptions setMaxSize(int maxSize) {
Arguments.require(maxSize > 0, "Max size must be > 0");
this.maxSize = maxSize;
return this;
}
public <K, V> Cache<K, V> newCache() {
return new CacheImpl<>(this);
}
@Override
public String toString() {
return toJson().toString();
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
CacheOptionsConverter.toJson(this, json);
return json;
}
}

View File

@ -0,0 +1,39 @@
package io.vertx.httpproxy.cache;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.impl.JsonUtil;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* Converter and mapper for {@link io.vertx.httpproxy.cache.CacheOptions}.
* NOTE: This class has been automatically generated from the {@link io.vertx.httpproxy.cache.CacheOptions} original class using Vert.x codegen.
*/
public class CacheOptionsConverter {
private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, CacheOptions obj) {
for (java.util.Map.Entry<String, Object> member : json) {
switch (member.getKey()) {
case "maxSize":
if (member.getValue() instanceof Number) {
obj.setMaxSize(((Number)member.getValue()).intValue());
}
break;
}
}
}
static void toJson(CacheOptions obj, JsonObject json) {
toJson(obj, json.getMap());
}
static void toJson(CacheOptions obj, java.util.Map<String, Object> json) {
json.put("maxSize", obj.getMaxSize());
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
public class BufferedReadStream implements ReadStream<Buffer> {
private long demand = 0L;
private Handler<Void> endHandler;
private Handler<Buffer> handler;
private boolean ended = false;
private final Buffer content;
public BufferedReadStream() {
this.content = Buffer.buffer();
}
public BufferedReadStream(Buffer content) {
this.content = content;
}
@Override
public ReadStream<Buffer> exceptionHandler(Handler<Throwable> handler) {
return this;
}
@Override
public ReadStream<Buffer> handler(Handler<Buffer> handler) {
this.handler = handler;
return this;
}
@Override
public ReadStream<Buffer> pause() {
demand = 0L;
return this;
}
@Override
public ReadStream<Buffer> resume() {
fetch(Long.MAX_VALUE);
return this;
}
@Override
public ReadStream<Buffer> fetch(long amount) {
if (!ended && amount > 0) {
ended = true;
demand += amount;
if (demand < 0L) {
demand = Long.MAX_VALUE;
}
if (demand != Long.MAX_VALUE) {
demand--;
}
if (handler != null && content.length() > 0) {
handler.handle(content);
}
if (endHandler != null) {
endHandler.handle(null);
}
}
return this;
}
@Override
public ReadStream<Buffer> endHandler(Handler<Void> endHandler) {
this.endHandler = endHandler;
return this;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
class BufferingReadStream implements ReadStream<Buffer> {
private final ReadStream<Buffer> stream;
private final Buffer content;
private Handler<Void> endHandler;
public BufferingReadStream(ReadStream<Buffer> stream, Buffer content) {
this.stream = stream;
this.content = content;
}
@Override
public ReadStream<Buffer> exceptionHandler(Handler<Throwable> handler) {
stream.exceptionHandler(handler);
return this;
}
@Override
public ReadStream<Buffer> handler(Handler<Buffer> handler) {
if (handler != null) {
stream.handler(buff -> {
content.appendBuffer(buff);
handler.handle(buff);
});
} else {
stream.handler(null);
}
return this;
}
@Override
public ReadStream<Buffer> pause() {
stream.pause();
return this;
}
@Override
public ReadStream<Buffer> resume() {
stream.resume();
return this;
}
@Override
public ReadStream<Buffer> fetch(long amount) {
stream.fetch(amount);
return this;
}
@Override
public ReadStream<Buffer> endHandler(Handler<Void> endHandler) {
if (endHandler != null) {
stream.endHandler(v -> {
endHandler.handle(null);
});
} else {
stream.endHandler(null);
}
return this;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
class BufferingWriteStream implements WriteStream<Buffer> {
private final Buffer content;
public BufferingWriteStream() {
this.content = Buffer.buffer();
}
public Buffer content() {
return content;
}
@Override
public WriteStream<Buffer> exceptionHandler(Handler<Throwable> handler) {
return this;
}
@Override
public Future<Void> write(Buffer data) {
content.appendBuffer(data);
return Future.succeededFuture();
}
@Override
public void write(Buffer data, Handler<AsyncResult<Void>> handler) {
content.appendBuffer(data);
handler.handle(Future.succeededFuture());
}
@Override
public void end(Handler<AsyncResult<Void>> handler) {
handler.handle(Future.succeededFuture());
}
@Override
public WriteStream<Buffer> setWriteQueueMaxSize(int maxSize) {
return this;
}
@Override
public boolean writeQueueFull() {
return false;
}
@Override
public WriteStream<Buffer> drainHandler(@Nullable Handler<Void> handler) {
return this;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class CacheControl {
private int maxAge;
private boolean _public;
public CacheControl parse(String header) {
maxAge = -1;
_public = false;
String[] parts = header.split(","); // No regex
for (String part : parts) {
part = part.trim().toLowerCase();
switch (part) {
case "public":
_public = true;
break;
default:
if (part.startsWith("max-age=")) {
maxAge = Integer.parseInt(part.substring(8));
}
break;
}
}
return this;
}
public int maxAge() {
return maxAge;
}
public boolean isPublic() {
return _public;
}
}

View File

@ -0,0 +1,23 @@
package io.vertx.httpproxy.impl;
import io.vertx.httpproxy.cache.CacheOptions;
import io.vertx.httpproxy.spi.cache.Cache;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Simplistic implementation.
*/
public class CacheImpl<K, V> extends LinkedHashMap<K, V> implements Cache<K, V> {
private final int maxSize;
public CacheImpl(CacheOptions options) {
this.maxSize = options.getMaxSize();
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
}

View File

@ -0,0 +1,150 @@
package io.vertx.httpproxy.impl;
import io.vertx.core.Future;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.ProxyContext;
import io.vertx.httpproxy.ProxyInterceptor;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import io.vertx.httpproxy.spi.cache.Cache;
import java.time.Instant;
import java.util.function.BiFunction;
class CachingFilter implements ProxyInterceptor {
private static final BiFunction<String, Resource, Resource> CACHE_GET_AND_VALIDATE = (key, resource) -> {
long now = System.currentTimeMillis();
long val = resource.timestamp + resource.maxAge;
return val < now ? null : resource;
};
private final Cache<String, Resource> cache;
public CachingFilter(Cache<String, Resource> cache) {
this.cache = cache;
}
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
Future<ProxyResponse> future = tryHandleProxyRequestFromCache(context);
if (future != null) {
return future;
}
return context.sendRequest();
}
@Override
public Future<Void> handleProxyResponse(ProxyContext context) {
return sendAndTryCacheProxyResponse(context);
}
private Future<Void> sendAndTryCacheProxyResponse(ProxyContext context) {
ProxyResponse response = context.response();
Resource cached = context.get("cached_resource", Resource.class);
if (cached != null && response.getStatusCode() == 304) {
// Warning: this relies on the fact that HttpServerRequest will not send a body for HEAD
response.release();
cached.init(response);
return context.sendResponse();
}
ProxyRequest request = response.request();
if (response.publicCacheControl() && response.maxAge() > 0) {
if (request.getMethod() == HttpMethod.GET) {
String absoluteUri = request.absoluteURI();
Resource res = new Resource(
absoluteUri,
response.getStatusCode(),
response.getStatusMessage(),
response.headers(),
System.currentTimeMillis(),
response.maxAge());
Body body = response.getBody();
response.setBody(Body.body(new BufferingReadStream(body.stream(), res.content), body.length()));
Future<Void> fut = context.sendResponse();
fut.onSuccess(v -> {
cache.put(absoluteUri, res);
});
return fut;
} else {
if (request.getMethod() == HttpMethod.HEAD) {
Resource resource = cache.get(request.absoluteURI());
if (resource != null) {
if (!revalidateResource(response, resource)) {
// Invalidate cache
cache.remove(request.absoluteURI());
}
}
}
return context.sendResponse();
}
} else {
return context.sendResponse();
}
}
private static boolean revalidateResource(ProxyResponse response, Resource resource) {
if (resource.etag != null && response.etag() != null) {
return resource.etag.equals(response.etag());
}
return true;
}
private Future<ProxyResponse> tryHandleProxyRequestFromCache(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
HttpServerRequest response = proxyRequest.proxiedRequest();
Resource resource;
HttpMethod method = response.method();
if (method == HttpMethod.GET || method == HttpMethod.HEAD) {
String cacheKey = proxyRequest.absoluteURI();
resource = cache.computeIfPresent(cacheKey, CACHE_GET_AND_VALIDATE);
if (resource == null) {
return null;
}
} else {
return null;
}
String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL);
if (cacheControlHeader != null) {
CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
if (cacheControl.maxAge() >= 0) {
long now = System.currentTimeMillis();
long currentAge = now - resource.timestamp;
if (currentAge > cacheControl.maxAge() * 1000) {
String etag = resource.headers.get(HttpHeaders.ETAG);
if (etag != null) {
proxyRequest.headers().set(HttpHeaders.IF_NONE_MATCH, resource.etag);
context.set("cached_resource", resource);
return context.sendRequest();
} else {
return null;
}
}
}
}
//
String ifModifiedSinceHeader = response.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
if ((response.method() == HttpMethod.GET || response.method() == HttpMethod.HEAD) && ifModifiedSinceHeader != null && resource.lastModified != null) {
Instant ifModifiedSince = ParseUtils.parseHeaderDate(ifModifiedSinceHeader);
if (!ifModifiedSince.isAfter(resource.lastModified)) {
response.response().setStatusCode(304).end();
return Future.succeededFuture();
}
}
proxyRequest.release();
ProxyResponse proxyResponse = proxyRequest.response();
resource.init(proxyResponse);
return Future.succeededFuture(proxyResponse);
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpHeaders;
import java.time.Instant;
import java.util.List;
class HttpUtils {
static Boolean isChunked(MultiMap headers) {
List<String> te = headers.getAll("transfer-encoding");
if (te != null) {
boolean chunked = false;
for (String val : te) {
if (val.equals("chunked")) {
chunked = true;
} else {
return null;
}
}
return chunked;
} else {
return false;
}
}
static Instant dateHeader(MultiMap headers) {
String dateHeader = headers.get(HttpHeaders.DATE);
if (dateHeader == null) {
List<String> warningHeaders = headers.getAll("warning");
if (warningHeaders.size() > 0) {
for (String warningHeader : warningHeaders) {
Instant date = ParseUtils.parseWarningHeaderDate(warningHeader);
if (date != null) {
return date;
}
}
}
return null;
} else {
return ParseUtils.parseHeaderDate(dateHeader);
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class ParseUtils {
public static final DateTimeFormatter RFC_850_DATE_TIME = new DateTimeFormatterBuilder()
.appendPattern("EEEE, dd-MMM-yy HH:mm:ss")
.parseLenient()
.appendLiteral(" GMT")
.toFormatter(Locale.US)
.withZone(ZoneId.of("UTC"));
public static final DateTimeFormatter ASC_TIME = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM d HH:mm:ss yyyy")
.parseLenient()
.toFormatter(Locale.US)
.withZone(ZoneId.of("UTC"));
public static Instant parseHeaderDate(String value) {
try {
return parseHttpDate(value);
} catch (Exception e) {
return null;
}
}
public static Instant parseWarningHeaderDate(String value) {
// warn-code
int index = value.indexOf(' ');
if (index > 0) {
// warn-agent
index = value.indexOf(' ', index + 1);
if (index > 0) {
// warn-text
index = value.indexOf(' ', index + 1);
if (index > 0) {
// warn-date
int len = value.length();
if (index + 2 < len && value.charAt(index + 1) == '"' && value.charAt(len - 1) == '"') {
// Space for 2 double quotes
return parseHeaderDate(value.substring(index + 2, len - 1));
}
}
}
}
return null;
}
public static String formatHttpDate(Instant date) {
return DateTimeFormatter.RFC_1123_DATE_TIME.format(OffsetDateTime.ofInstant(date, ZoneOffset.UTC));
}
// https://www.rfc-editor.org/rfc/rfc9110#http.date
public static Instant parseHttpDate(String value) throws Exception {
int pos = value.indexOf(',');
if (pos == 3) { // e.g. Sun, 06 Nov 1994 08:49:37 GMT
return DateTimeFormatter.RFC_1123_DATE_TIME.parse(value, Instant::from);
}
if (pos == -1) { // e.g. Sun Nov 6 08:49:37 1994
return ASC_TIME.parse(value, Instant::from);
}
return RFC_850_DATE_TIME.parse(value, Instant::from); // e.g. Sunday, 06-Nov-94 08:49:37 GMT
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.impl.HttpServerRequestInternal;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.streams.Pipe;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import java.util.Map;
import java.util.Objects;
public class ProxiedRequest implements ProxyRequest {
private static final CharSequence X_FORWARDED_HOST = HttpHeaders.createOptimized("x-forwarded-host");
private static final MultiMap HOP_BY_HOP_HEADERS = MultiMap.caseInsensitiveMultiMap()
.add(HttpHeaders.CONNECTION, "whatever")
.add(HttpHeaders.KEEP_ALIVE, "whatever")
.add(HttpHeaders.PROXY_AUTHENTICATE, "whatever")
.add(HttpHeaders.PROXY_AUTHORIZATION, "whatever")
.add("te", "whatever")
.add("trailer", "whatever")
.add(HttpHeaders.TRANSFER_ENCODING, "whatever")
.add(HttpHeaders.UPGRADE, "whatever");
final ContextInternal context;
private HttpMethod method;
private final HttpVersion version;
private String uri;
private final String absoluteURI;
private Body body;
private HostAndPort authority;
private final MultiMap headers;
HttpClientRequest request;
private final HttpServerRequest proxiedRequest;
public ProxiedRequest(HttpServerRequest proxiedRequest) {
// Determine content length
long contentLength = -1L;
String contentLengthHeader = proxiedRequest.getHeader(HttpHeaders.CONTENT_LENGTH);
if (contentLengthHeader != null) {
try {
contentLength = Long.parseLong(contentLengthHeader);
} catch (NumberFormatException e) {
// Ignore ???
}
}
this.method = proxiedRequest.method();
this.version = proxiedRequest.version();
this.body = Body.body(proxiedRequest, contentLength);
this.uri = proxiedRequest.uri();
this.headers = MultiMap.caseInsensitiveMultiMap().addAll(proxiedRequest.headers());
this.absoluteURI = proxiedRequest.absoluteURI();
this.proxiedRequest = proxiedRequest;
this.context = (ContextInternal) ((HttpServerRequestInternal) proxiedRequest).context();
this.authority = proxiedRequest.authority();
}
@Override
public HttpVersion version() {
return version;
}
@Override
public String getURI() {
return uri;
}
@Override
public ProxyRequest setURI(String uri) {
this.uri = uri;
return this;
}
@Override
public Body getBody() {
return body;
}
@Override
public ProxyRequest setBody(Body body) {
this.body = body;
return this;
}
@Override
public ProxyRequest setAuthority(HostAndPort authority) {
Objects.requireNonNull(authority);
this.authority= authority;
return this;
}
@Override
public HostAndPort getAuthority() {
return authority;
}
@Override
public String absoluteURI() {
return absoluteURI;
}
@Override
public HttpMethod getMethod() {
return method;
}
@Override
public ProxyRequest setMethod(HttpMethod method) {
this.method = method;
return this;
}
@Override
public HttpServerRequest proxiedRequest() {
return proxiedRequest;
}
@Override
public ProxyRequest release() {
body.stream().resume();
headers.clear();
body = null;
return this;
}
@Override
public ProxyResponse response() {
return new ProxiedResponse(this, proxiedRequest.response());
}
void sendRequest(Handler<AsyncResult<ProxyResponse>> responseHandler) {
request.response().<ProxyResponse>map(r -> {
r.pause(); // Pause it
return new ProxiedResponse(this, proxiedRequest.response(), r);
}).onComplete(responseHandler);
request.setMethod(method);
request.setURI(uri);
// Add all headers
for (Map.Entry<String, String> header : headers) {
String name = header.getKey();
String value = header.getValue();
if (!HOP_BY_HOP_HEADERS.contains(name) && !name.equals("host")) {
request.headers().add(name, value);
}
}
//
if (authority != null) {
request.authority(authority);
HostAndPort proxiedAuthority = proxiedRequest.authority();
if (!equals(authority, proxiedAuthority)) {
// Should cope with existing forwarded host headers
request.putHeader(X_FORWARDED_HOST, proxiedAuthority.toString());
}
}
long len = body.length();
if (len >= 0) {
request.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
} else {
Boolean isChunked = HttpUtils.isChunked(proxiedRequest.headers());
request.setChunked(len == -1 && Boolean.TRUE == isChunked);
}
Pipe<Buffer> pipe = body.stream().pipe();
pipe.endOnComplete(true);
pipe.endOnFailure(false);
pipe.to(request, ar -> {
if (ar.failed()) {
request.reset();
}
});
}
private static boolean equals(HostAndPort hp1, HostAndPort hp2) {
if (hp1 == null || hp2 == null) {
return false;
}
return hp1.host().equals(hp2.host()) && hp1.port() == hp2.port();
}
@Override
public ProxyRequest putHeader(CharSequence name, CharSequence value) {
headers.set(name, value);
return this;
}
@Override
public MultiMap headers() {
return headers;
}
@Override
public Future<ProxyResponse> send(HttpClientRequest request) {
Promise<ProxyResponse> promise = context.promise();
this.request = request;
sendRequest(promise);
return promise.future();
}
}

View File

@ -0,0 +1,271 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.streams.Pipe;
import io.vertx.core.streams.ReadStream;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class ProxiedResponse implements ProxyResponse {
private final ProxiedRequest request;
private final HttpServerResponse proxiedResponse;
private int statusCode;
private String statusMessage;
private Body body;
private final MultiMap headers;
private HttpClientResponse response;
private long maxAge;
private String etag;
private boolean publicCacheControl;
ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse) {
this.response = null;
this.statusCode = 200;
this.headers = MultiMap.caseInsensitiveMultiMap();
this.request = request;
this.proxiedResponse = proxiedResponse;
}
ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse, HttpClientResponse response) {
// Determine content length
long contentLength = -1L;
String contentLengthHeader = response.getHeader(HttpHeaders.CONTENT_LENGTH);
if (contentLengthHeader != null) {
try {
contentLength = Long.parseLong(contentLengthHeader);
} catch (NumberFormatException e) {
// Ignore ???
}
}
this.request = request;
this.response = response;
this.proxiedResponse = proxiedResponse;
this.statusCode = response.statusCode();
this.statusMessage = response.statusMessage();
this.body = Body.body(response, contentLength);
long maxAge = -1;
boolean publicCacheControl = false;
String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL);
if (cacheControlHeader != null) {
CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
if (cacheControl.isPublic()) {
publicCacheControl = true;
if (cacheControl.maxAge() > 0) {
maxAge = (long)cacheControl.maxAge() * 1000;
} else {
String dateHeader = response.getHeader(HttpHeaders.DATE);
String expiresHeader = response.getHeader(HttpHeaders.EXPIRES);
if (dateHeader != null && expiresHeader != null) {
maxAge = ParseUtils.parseHeaderDate(expiresHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli();
}
}
}
}
this.maxAge = maxAge;
this.publicCacheControl = publicCacheControl;
this.etag = response.getHeader(HttpHeaders.ETAG);
this.headers = MultiMap.caseInsensitiveMultiMap().addAll(response.headers());
}
@Override
public ProxyRequest request() {
return request;
}
@Override
public int getStatusCode() {
return statusCode;
}
@Override
public ProxyResponse setStatusCode(int sc) {
statusCode = sc;
return this;
}
@Override
public String getStatusMessage() {
return statusMessage;
}
@Override
public ProxyResponse setStatusMessage(String statusMessage) {
this.statusMessage = statusMessage;
return this;
}
@Override
public Body getBody() {
return body;
}
@Override
public ProxyResponse setBody(Body body) {
this.body = body;
return this;
}
@Override
public boolean publicCacheControl() {
return publicCacheControl;
}
@Override
public long maxAge() {
return maxAge;
}
@Override
public String etag() {
return etag;
}
@Override
public MultiMap headers() {
return headers;
}
@Override
public ProxyResponse putHeader(CharSequence name, CharSequence value) {
headers.set(name, value);
return this;
}
@Override
public Future<Void> send() {
Promise<Void> promise = request.context.promise();
send(promise);
return promise.future();
}
public void send(Handler<AsyncResult<Void>> completionHandler) {
// Set stuff
proxiedResponse.setStatusCode(statusCode);
if(statusMessage != null) {
proxiedResponse.setStatusMessage(statusMessage);
}
// Date header
Instant date = HttpUtils.dateHeader(headers);
if (date == null) {
date = Instant.now();
}
try {
proxiedResponse.putHeader("date", ParseUtils.formatHttpDate(date));
} catch (Exception e) {
e.printStackTrace();
}
// Warning header
List<String> warningHeaders = headers.getAll("warning");
if (warningHeaders.size() > 0) {
warningHeaders = new ArrayList<>(warningHeaders);
String dateHeader = headers.get("date");
Instant dateInstant = dateHeader != null ? ParseUtils.parseHeaderDate(dateHeader) : null;
Iterator<String> i = warningHeaders.iterator();
// Suppress incorrect warning header
while (i.hasNext()) {
String warningHeader = i.next();
Instant warningInstant = ParseUtils.parseWarningHeaderDate(warningHeader);
if (warningInstant != null && dateInstant != null && !warningInstant.equals(dateInstant)) {
i.remove();
}
}
}
proxiedResponse.putHeader("warning", warningHeaders);
// Handle other headers
headers.forEach(header -> {
String name = header.getKey();
String value = header.getValue();
if (name.equalsIgnoreCase("date") || name.equalsIgnoreCase("warning") || name.equalsIgnoreCase("transfer-encoding")) {
// Skip
} else {
proxiedResponse.headers().add(name, value);
}
});
//
if (body == null) {
proxiedResponse.end();
return;
}
long len = body.length();
if (len >= 0) {
proxiedResponse.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
} else {
if (request.proxiedRequest().version() == HttpVersion.HTTP_1_0) {
// Special handling for HTTP 1.0 clients that cannot handle chunked encoding
// we need to buffer the content
BufferingWriteStream buffer = new BufferingWriteStream();
body.stream().pipeTo(buffer, ar -> {
if (ar.succeeded()) {
Buffer content = buffer.content();
proxiedResponse.end(content, completionHandler);
} else {
System.out.println("Not implemented");
}
});
return;
}
proxiedResponse.setChunked(true);
}
ReadStream<Buffer> bodyStream = body.stream();
sendResponse(bodyStream, completionHandler);
}
@Override
public ProxyResponse release() {
if (response != null) {
response.resume();
response = null;
body = null;
headers.clear();
}
return this;
}
private void sendResponse(ReadStream<Buffer> body, Handler<AsyncResult<Void>> completionHandler) {
Pipe<Buffer> pipe = body.pipe();
pipe.endOnSuccess(true);
pipe.endOnFailure(false);
pipe.to(proxiedResponse, ar -> {
if (ar.failed()) {
request.request.reset();
proxiedResponse.reset();
}
completionHandler.handle(ar);
});
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.ProxyResponse;
import java.time.Instant;
class Resource {
final String absoluteUri;
final int statusCode;
final String statusMessage;
final MultiMap headers;
final long timestamp;
final long maxAge;
final Instant lastModified;
final String etag;
final Buffer content = Buffer.buffer();
Resource(String absoluteUri, int statusCode, String statusMessage, MultiMap headers, long timestamp, long maxAge) {
String lastModifiedHeader = headers.get(HttpHeaders.LAST_MODIFIED);
this.absoluteUri = absoluteUri;
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.headers = headers;
this.timestamp = timestamp;
this.maxAge = maxAge;
this.lastModified = lastModifiedHeader != null ? ParseUtils.parseHeaderDate(lastModifiedHeader) : null;
this.etag = headers.get(HttpHeaders.ETAG);
}
void init(ProxyResponse proxyResponse) {
proxyResponse.setStatusCode(200);
proxyResponse.setStatusMessage(statusMessage);
proxyResponse.headers().addAll(headers);
proxyResponse.setBody(Body.body(content));
}
}

View File

@ -0,0 +1,299 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.httpproxy.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.BiFunction;
import org.apache.commons.lang3.StringUtils;
import com.sf.vertx.init.DynamicBuildServer;
import com.sf.vertx.security.MainSecurity;
import com.sf.vertx.service.impl.AppConfigServiceImpl;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.NetSocket;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.HttpProxy;
import io.vertx.httpproxy.ProxyContext;
import io.vertx.httpproxy.ProxyInterceptor;
import io.vertx.httpproxy.ProxyOptions;
import io.vertx.httpproxy.ProxyRequest;
import io.vertx.httpproxy.ProxyResponse;
import io.vertx.httpproxy.cache.CacheOptions;
import io.vertx.httpproxy.spi.cache.Cache;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ReverseProxy implements HttpProxy {
private final HttpClient client;
private final boolean supportWebSocket;
private BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> selector = (req, client) -> Future
.failedFuture("No origin available");
private final List<ProxyInterceptor> interceptors = new ArrayList<>();
private RoutingContext ctx;
private WebClient mainWebClient;
public ReverseProxy(ProxyOptions options, HttpClient client) {
CacheOptions cacheOptions = options.getCacheOptions();
if (cacheOptions != null) {
Cache<String, Resource> cache = cacheOptions.newCache();
addInterceptor(new CachingFilter(cache));
}
this.client = client;
this.supportWebSocket = options.getSupportWebSocket();
}
@Override
public HttpProxy originRequestProvider(
BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> provider) {
selector = provider;
return this;
}
@Override
public HttpProxy addInterceptor(ProxyInterceptor interceptor) {
interceptors.add(interceptor);
return this;
}
@Override
public void handle(RoutingContext ctx, WebClient mainWebClient) {
// TODO 改造了这个地方
this.ctx = ctx;
this.mainWebClient = mainWebClient;
handle(ctx.request());
}
@Override
public void handle(HttpServerRequest request) {
ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request);
// Encoding sanity check
Boolean chunked = HttpUtils.isChunked(request.headers());
if (chunked == null) {
end(proxyRequest, 400);
return;
}
// WebSocket upgrade tunneling
if (supportWebSocket && io.vertx.core.http.impl.HttpUtils.canUpgradeToWebSocket(request)) {
handleWebSocketUpgrade(proxyRequest);
return;
}
Proxy proxy = new Proxy(proxyRequest);
proxy.filters = interceptors.listIterator();
proxy.sendRequest().compose(proxy::sendProxyResponse);
}
private void handleWebSocketUpgrade(ProxyRequest proxyRequest) {
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
resolveOrigin(proxiedRequest).onComplete(ar -> {
if (ar.succeeded()) {
HttpClientRequest request = ar.result();
request.setMethod(HttpMethod.GET);
request.setURI(proxiedRequest.uri());
request.headers().addAll(proxiedRequest.headers());
Future<HttpClientResponse> fut2 = request.connect();
proxiedRequest.handler(request::write);
proxiedRequest.endHandler(v -> request.end());
proxiedRequest.resume();
fut2.onComplete(ar2 -> {
if (ar2.succeeded()) {
HttpClientResponse proxiedResponse = ar2.result();
if (proxiedResponse.statusCode() == 101) {
HttpServerResponse response = proxiedRequest.response();
response.setStatusCode(101);
response.headers().addAll(proxiedResponse.headers());
Future<NetSocket> otherso = proxiedRequest.toNetSocket();
otherso.onComplete(ar3 -> {
if (ar3.succeeded()) {
NetSocket responseSocket = ar3.result();
NetSocket proxyResponseSocket = proxiedResponse.netSocket();
responseSocket.handler(proxyResponseSocket::write);
proxyResponseSocket.handler(responseSocket::write);
responseSocket.closeHandler(v -> proxyResponseSocket.close());
proxyResponseSocket.closeHandler(v -> responseSocket.close());
} else {
// Find reproducer
System.err.println("Handle this case");
ar3.cause().printStackTrace();
}
});
} else {
// Rejection
proxiedRequest.resume();
end(proxyRequest, proxiedResponse.statusCode());
}
} else {
proxiedRequest.resume();
end(proxyRequest, 502);
}
});
} else {
proxiedRequest.resume();
end(proxyRequest, 502);
}
});
}
private void end(ProxyRequest proxyRequest, int sc) {
proxyRequest.response().release().setStatusCode(sc).putHeader(HttpHeaders.CONTENT_LENGTH, "0").setBody(null)
.send();
}
private Future<HttpClientRequest> resolveOrigin(HttpServerRequest proxiedRequest) {
return selector.apply(proxiedRequest, client);
}
private class Proxy implements ProxyContext {
private final ProxyRequest request;
private ProxyResponse response;
private final Map<String, Object> attachments = new HashMap<>();
private ListIterator<ProxyInterceptor> filters;
private Proxy(ProxyRequest request) {
this.request = request;
}
@Override
public void set(String name, Object value) {
attachments.put(name, value);
}
@Override
public <T> T get(String name, Class<T> type) {
Object o = attachments.get(name);
return type.isInstance(o) ? type.cast(o) : null;
}
@Override
public ProxyRequest request() {
return request;
}
@Override
public Future<ProxyResponse> sendRequest() {
if (filters.hasNext()) {
ProxyInterceptor next = filters.next();
return next.handleProxyRequest(this);
} else {
return sendProxyRequest(request);
}
}
@Override
public ProxyResponse response() {
return response;
}
@Override
public Future<Void> sendResponse() {
if (filters.hasPrevious()) {
ProxyInterceptor filter = filters.previous();
return filter.handleProxyResponse(this);
} else {
return response.send();
}
}
private Future<ProxyResponse> sendProxyRequest(ProxyRequest proxyRequest) {
// TODO 改造了这个地方
// 创建一个响应并设置参数
// ctx.request().response().setStatusCode(200)
// .putHeader("content-type", "text/plain").end(body);
// 发起一个请求
String sacAppHeaderKey = proxyRequest.headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
String body = ctx.getBodyAsString();
String bodyData = MainSecurity.aesDecrypt(body, "dadddsdfadfadsfa33323223");
return Future.future(p -> {
mainWebClient.post(9198, "127.0.0.1", "/vertx/body").sendJson(bodyData, h -> {
if (h.succeeded()) {
// 释放资源
proxyRequest.release();
JsonObject responseData = h.result().bodyAsJsonObject();
log.info("responseData:{}", responseData);
// 加密
String dataStr = MainSecurity.aesEncrypt(responseData.toString(), "dadddsdfadfadsfa33323223");
log.info("aesEncrypt dataStr:{}", dataStr);
ctx.request().response().setStatusCode(200).putHeader("content-type", "application/json")
.end(dataStr);
Buffer buffer = Buffer.buffer(dataStr);
ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
.putHeader("content-type", "application/json").setBody(Body.body(buffer));
p.complete(proxyResponse);
} else {
p.fail(h.cause());
}
});
});
} else {
Future<HttpClientRequest> f = resolveOrigin(proxyRequest.proxiedRequest());
f.onFailure(err -> {
// Should this be done here ? I don't think so
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
proxiedRequest.resume();
Promise<Void> promise = Promise.promise();
proxiedRequest.exceptionHandler(promise::tryFail);
proxiedRequest.endHandler(promise::tryComplete);
promise.future().onComplete(ar2 -> {
end(proxyRequest, 502);
});
});
return f.compose(a -> sendProxyRequest(proxyRequest, a));
}
}
private Future<ProxyResponse> sendProxyRequest(ProxyRequest proxyRequest, HttpClientRequest request) {
Future<ProxyResponse> fut = proxyRequest.send(request);
fut.onFailure(err -> {
proxyRequest.proxiedRequest().response().setStatusCode(502).end();
});
return fut;
}
private Future<Void> sendProxyResponse(ProxyResponse response) {
this.response = response;
// Check validity
Boolean chunked = HttpUtils.isChunked(response.headers());
if (chunked == null) {
// response.request().release(); // Is it needed ???
end(response.request(), 501);
return Future.succeededFuture(); // should use END future here ???
}
return sendResponse();
}
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@ModuleGen(name = "vertx-http-proxy", groupPackage = "io.vertx", useFutures = true)
package io.vertx.httpproxy;
import io.vertx.codegen.annotations.ModuleGen;

View File

@ -0,0 +1,9 @@
package io.vertx.httpproxy.spi.cache;
import java.util.Map;
/**
* Cache SPI.
*/
public interface Cache<K, V> extends Map<K, V> {
}