源码改造, 反向代理支持加解密、非加密传输
This commit is contained in:
parent
87f61bc4d8
commit
99fcb47f55
@ -96,10 +96,6 @@
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-http-proxy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
|
@ -77,6 +77,7 @@ public class BodyHandlerImpl implements BodyHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext context) {
|
||||
// TODO 改造了这个地方
|
||||
final HttpServerRequest request = context.request();
|
||||
final HttpServerResponse response = context.response();
|
||||
String sacAppHeaderKey = request.getHeader(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
||||
|
@ -1,25 +1,12 @@
|
||||
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.client.WebClient;
|
||||
import io.vertx.httpproxy.Body;
|
||||
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>
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyHandlerImpl implements ProxyHandler {
|
||||
|
||||
private final HttpProxy httpProxy;
|
||||
@ -40,23 +27,9 @@ public class ProxyHandlerImpl implements ProxyHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext ctx) {
|
||||
// 创建一个响应并设置参数
|
||||
// ctx.request().response().setStatusCode(200)
|
||||
// .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);
|
||||
// }
|
||||
// });
|
||||
|
||||
// TODO 改造了这个地方
|
||||
httpProxy.handle(ctx, mainWebClient);
|
||||
// 原始代码只有如下一句
|
||||
httpProxy.handle(ctx.request());
|
||||
// httpProxy.handle(ctx.request());
|
||||
}
|
||||
}
|
@ -119,8 +119,9 @@ public class DynamicBuildServer implements ApplicationRunner {
|
||||
return context.sendRequest();
|
||||
}
|
||||
});
|
||||
// mainHttpRouter.route().handler(ProxyHandler.create(proxy));
|
||||
WebClient mainWebClient = WebClient.create(VERTX);
|
||||
//mainHttpRouter.route().handler(ProxyHandler.create(proxy));
|
||||
|
||||
mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy));
|
||||
}
|
||||
|
||||
@ -136,6 +137,7 @@ public class DynamicBuildServer implements ApplicationRunner {
|
||||
SacLoadBalancing sacLoadBalancing = null;
|
||||
// 2、是否存在server服务配置
|
||||
if (appConfig.getService() != null && appConfig.getService().size() > 0) {
|
||||
// header传递服务名, 这样就不需要遍历,提高性能
|
||||
for (SacService service : appConfig.getService()) {
|
||||
// uri是否匹配
|
||||
boolean match = false;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
182
sf-vertx/src/main/java/examples/HttpProxyExamples.java
Normal file
182
sf-vertx/src/main/java/examples/HttpProxyExamples.java
Normal 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);
|
||||
}
|
||||
}
|
6
sf-vertx/src/main/java/examples/package-info.java
Normal file
6
sf-vertx/src/main/java/examples/package-info.java
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
|
||||
*/
|
||||
@Source
|
||||
package examples;
|
||||
import io.vertx.docgen.Source;
|
91
sf-vertx/src/main/java/io/vertx/httpproxy/Body.java
Normal file
91
sf-vertx/src/main/java/io/vertx/httpproxy/Body.java
Normal 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();
|
||||
|
||||
}
|
122
sf-vertx/src/main/java/io/vertx/httpproxy/HttpProxy.java
Normal file
122
sf-vertx/src/main/java/io/vertx/httpproxy/HttpProxy.java
Normal 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);
|
||||
}
|
48
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyContext.java
Normal file
48
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyContext.java
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
79
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptions.java
Normal file
79
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyOptions.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
181
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyRequest.java
Normal file
181
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyRequest.java
Normal 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();
|
||||
|
||||
}
|
133
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyResponse.java
Normal file
133
sf-vertx/src/main/java/io/vertx/httpproxy/ProxyResponse.java
Normal 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();
|
||||
|
||||
}
|
61
sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java
vendored
Normal file
61
sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptions.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
39
sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptionsConverter.java
vendored
Normal file
39
sf-vertx/src/main/java/io/vertx/httpproxy/cache/CacheOptionsConverter.java
vendored
Normal 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
51
sf-vertx/src/main/java/io/vertx/httpproxy/impl/Resource.java
Normal file
51
sf-vertx/src/main/java/io/vertx/httpproxy/impl/Resource.java
Normal 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));
|
||||
}
|
||||
}
|
299
sf-vertx/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java
Normal file
299
sf-vertx/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
18
sf-vertx/src/main/java/io/vertx/httpproxy/package-info.java
Normal file
18
sf-vertx/src/main/java/io/vertx/httpproxy/package-info.java
Normal 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;
|
9
sf-vertx/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java
vendored
Normal file
9
sf-vertx/src/main/java/io/vertx/httpproxy/spi/cache/Cache.java
vendored
Normal 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> {
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user