vertx加解密

This commit is contained in:
ztzh_xieyun 2024-04-24 14:38:49 +08:00
parent 4a24397f08
commit e922dfbc4a
23 changed files with 957 additions and 183 deletions

View File

@ -0,0 +1,16 @@
package com.sf.vertx.api.pojo;
import java.io.Serializable;
import lombok.Data;
@Data
public class AdvancedConfig implements Serializable {
private static final long serialVersionUID = -6935223493505821401L;
private Integer retryStrategy; // 请求失败重试次数
private Integer timeout; // 请求超时时间
private String cacheConfig; // 响应数据的缓存策略
private String zipConfig;// 响应压缩
private String monitorCconfig;// 监控指标
}

View File

@ -8,8 +8,11 @@ import lombok.Data;
@Data
public class AppConfig implements Serializable {
private static final long serialVersionUID = 1518165296680157119L;
private String appCode;
private String appCode; // 应用唯一码, app访问uri添加前缀,用于网关区分多应用
private boolean exclusiveService; // 预留字段, 独立端口
private List<Node> nodeList;
private List<Strategy> strategyList;
private Integer exclusiveGatewayConfigId; // 独享网关配置编号
private EnvironmentConfig environmentConfig; // 环境配置
private List<SacService> service; // 服务
private AdvancedConfig advancedConfig; // 高级配置
private DataSecurity dataSecurity; // 数据加解密
}

View File

@ -7,6 +7,7 @@ import lombok.Data;
@Data
public class DataSecurity implements Serializable {
private static final long serialVersionUID = 5034274428665340830L;
private String key; //加密串
private String algorithm; // 国密加解密
private String publicKey; // 公钥
private String privateKey; // 私钥

View File

@ -0,0 +1,17 @@
package com.sf.vertx.api.pojo;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import lombok.Data;
@Data
public class EnvironmentConfig implements Serializable {
private static final long serialVersionUID = -3952046909425019869L;
private Integer defaultId; // 默认环境配置编号
private Map<Integer, List<Node>> environmentGroup; // 环境节点
}

View File

@ -8,5 +8,6 @@ import lombok.Data;
public class GatewayInterface implements Serializable {
private static final long serialVersionUID = -313734935498432381L;
private String uri;
private boolean uriRegular; // uri 正则
private String method; // 大写
}

View File

@ -0,0 +1,16 @@
package com.sf.vertx.api.pojo;
import java.io.Serializable;
import java.util.Map;
import lombok.Data;
@Data
public class Router implements Serializable {
private static final long serialVersionUID = -4471811880134025210L;
private String domain; // 域名
private Map<String, String> headers; // 头字段
private String headerVal; // 头字段
private Integer environmentId; // 环境配置编号
}

View File

@ -0,0 +1,15 @@
package com.sf.vertx.api.pojo;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@Data
public class SacService implements Serializable {
private static final long serialVersionUID = -5171112142954536813L;
private Strategy strategy; // 策略
private List<GatewayInterface> uriList; // uri列表
private Router router; // 路由
}

View File

@ -1,7 +1,6 @@
package com.sf.vertx.api.pojo;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@ -13,9 +12,5 @@ import lombok.Data;
@Data
public class Strategy implements Serializable {
private static final long serialVersionUID = -8831406773224882471L;
private DataSecurity dataSecurity;
private List<GatewayInterface> gatewayInterfaceList;
private HttpClientOptionsConfig httpClientOptionsConfig; // 高并发情况,配置接口连接池
// 限流熔断
}

View File

@ -9,5 +9,6 @@ public class VertxConfig implements Serializable {
private static final long serialVersionUID = -1706421732809219829L;
private Integer port; // 启动端口
private VertxOptionsConfig vertxOptionsConfig;
private HttpClientOptionsConfig httpClientOptionsConfig; // 配置Vert端口连接池
}

View File

@ -25,7 +25,7 @@ public class AppConfigController {
private AppConfigService appConfigService;
@PostMapping("/addAppConfig")
public String addAppConfig(@RequestBody AppConfig appConfig) {
public String addAppConfig(@RequestBody String appConfig) {
appConfigService.addAppConfig(appConfig);
return null;
}

View File

@ -0,0 +1,144 @@
package com.sf.vertx.handle;
/*
* Copyright 2014 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
/**
* A handler which gathers the entire request body and sets it on the {@link RoutingContext}.
* <p>
* It also handles HTTP file uploads and can be used to limit body sizes.
*
* @author <a href="http://tfox.org">Tim Fox</a>
*/
@VertxGen
public interface BodyHandler extends Handler<RoutingContext> {
/**
* Default max size for a request body in bytes = {@code 10485760}, i.e. 10 megabytes
*/
long DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
/**
* Default uploads directory on server for file uploads
*/
String DEFAULT_UPLOADS_DIRECTORY = "file-uploads";
/**
* Default value of whether form attributes should be merged into request params
*/
boolean DEFAULT_MERGE_FORM_ATTRIBUTES = true;
/**
* Default value of whether uploaded files should be removed after handling the request
*/
boolean DEFAULT_DELETE_UPLOADED_FILES_ON_END = false;
/**
* Default value of whether to pre-allocate the body buffer size according to the content-length HTTP request header
*/
boolean DEFAULT_PREALLOCATE_BODY_BUFFER = false;
/**
* Create a body handler with defaults.
*
* @return the body handler
*/
static BodyHandler create() {
return new BodyHandlerImpl();
}
/**
* Create a body handler setting if it should handle file uploads.
*
* @param handleFileUploads true if files upload should be handled
* @return the body handler
*/
static BodyHandler create(boolean handleFileUploads) {
return new BodyHandlerImpl(handleFileUploads);
}
/**
* Create a body handler and use the given upload directory.
*
* @param uploadDirectory the uploads directory
* @return the body handler
*/
static BodyHandler create(String uploadDirectory) {
return new BodyHandlerImpl(uploadDirectory);
}
/**
* Set whether file uploads will be handled.
*
* @param handleFileUploads true if they should be handled
* @return reference to this for fluency
*/
@Fluent
BodyHandler setHandleFileUploads(boolean handleFileUploads);
/**
* Set the maximum body size in bytes, {@code -1} means no limit.
*
* @param bodyLimit the max size in bytes
* @return reference to this for fluency
*/
@Fluent
BodyHandler setBodyLimit(long bodyLimit);
/**
* Set the uploads directory to use.
*
* @param uploadsDirectory the uploads directory
* @return reference to this for fluency
*/
@Fluent
BodyHandler setUploadsDirectory(String uploadsDirectory);
/**
* Set whether form attributes will be added to the request parameters.
*
* @param mergeFormAttributes true if they should be merged
* @return reference to this for fluency
*/
@Fluent
BodyHandler setMergeFormAttributes(boolean mergeFormAttributes);
/**
* Set whether uploaded files should be removed after handling the request.
*
* @param deleteUploadedFilesOnEnd true if uploaded files should be removed after handling the request
* @return reference to this for fluency
*/
@Fluent
BodyHandler setDeleteUploadedFilesOnEnd(boolean deleteUploadedFilesOnEnd);
/**
* Pre-allocate the body buffer according to the value parsed from content-length header.
* The buffer is capped at 64KB
* @param isPreallocateBodyBuffer {@code true} if body buffer is pre-allocated according to the size
* read from content-length Header.
* {code false} if body buffer is pre-allocated to 1KB, and is resized dynamically
* @return reference to this for fluency
*/
@Fluent
BodyHandler setPreallocateBodyBuffer(boolean isPreallocateBodyBuffer);
}

View File

@ -0,0 +1,382 @@
package com.sf.vertx.handle;
/*
* Copyright 2014 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.FileSystem;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.impl.FileUploadImpl;
import io.vertx.ext.web.impl.RoutingContextInternal;
import java.io.File;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;
import com.sf.vertx.init.DynamicBuildServer;
import com.sf.vertx.service.impl.AppConfigServiceImpl;
/**
* @author <a href="http://tfox.org">Tim Fox</a>
*/
public class BodyHandlerImpl implements BodyHandler {
private static final Logger LOG = LoggerFactory.getLogger(BodyHandlerImpl.class);
private long bodyLimit = DEFAULT_BODY_LIMIT;
private boolean handleFileUploads;
private String uploadsDir;
private boolean mergeFormAttributes = DEFAULT_MERGE_FORM_ATTRIBUTES;
private boolean deleteUploadedFilesOnEnd = DEFAULT_DELETE_UPLOADED_FILES_ON_END;
private boolean isPreallocateBodyBuffer = DEFAULT_PREALLOCATE_BODY_BUFFER;
private static final int DEFAULT_INITIAL_BODY_BUFFER_SIZE = 1024; // bytes
public BodyHandlerImpl() {
this(true, DEFAULT_UPLOADS_DIRECTORY);
}
public BodyHandlerImpl(boolean handleFileUploads) {
this(handleFileUploads, DEFAULT_UPLOADS_DIRECTORY);
}
public BodyHandlerImpl(String uploadDirectory) {
this(true, uploadDirectory);
}
private BodyHandlerImpl(boolean handleFileUploads, String uploadDirectory) {
this.handleFileUploads = handleFileUploads;
setUploadsDirectory(uploadDirectory);
}
@Override
public void handle(RoutingContext context) {
final HttpServerRequest request = context.request();
final HttpServerResponse response = context.response();
String sacAppHeaderKey = request.getHeader(DynamicBuildServer.SAC_APP_HEADER_KEY);
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
// 加解密在proxy拦截器解析跳转
// =======源码流程
// final HttpServerRequest request = context.request();
// final HttpServerResponse response = context.response();
//
// we need to keep state since we can be called again on reroute
if (!((RoutingContextInternal) context).seenHandler(RoutingContextInternal.BODY_HANDLER)) {
((RoutingContextInternal) context).visitHandler(RoutingContextInternal.BODY_HANDLER);
// Check if a request has a request body.
// A request with a body __must__ either have `transfer-encoding`
// or `content-length` headers set.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
final long parsedContentLength = parseContentLengthHeader(request);
// http2 never transmits a `transfer-encoding` as frames are chunks.
final boolean hasTransferEncoding = request.version() == HttpVersion.HTTP_2
|| request.headers().contains(HttpHeaders.TRANSFER_ENCODING);
if (!hasTransferEncoding && parsedContentLength == -1) {
// there is no "body", so we can skip this handler
context.next();
return;
}
// before parsing the body we can already discard a bad request just by
// inspecting the content-length against
// the body limit, this will reduce load, on the server by totally skipping
// parsing the request body
if (bodyLimit != -1 && parsedContentLength != -1) {
if (parsedContentLength > bodyLimit) {
context.fail(413);
return;
}
}
// handle expectations
// https://httpwg.org/specs/rfc7231.html#header.expect
final String expect = request.getHeader(HttpHeaders.EXPECT);
if (expect != null) {
// requirements validation
if (expect.equalsIgnoreCase("100-continue")) {
// A server that receives a 100-continue expectation in an HTTP/1.0 request MUST
// ignore that expectation.
if (request.version() != HttpVersion.HTTP_1_0) {
// signal the client to continue
response.writeContinue();
}
} else {
// the server cannot meet the expectation, we only know about 100-continue
context.fail(417);
return;
}
}
final BHandler handler = new BHandler(context, isPreallocateBodyBuffer ? parsedContentLength : -1);
boolean ended = request.isEnded();
if (!ended) {
request
// resume the request (if paused)
.handler(handler).endHandler(handler::end).resume();
}
} else {
// on reroute we need to re-merge the form params if that was desired
if (mergeFormAttributes && request.isExpectMultipart()) {
request.params().addAll(request.formAttributes());
}
context.next();
}
} else {
// 非加解密不处理
context.next();
return;
}
}
@Override
public BodyHandler setHandleFileUploads(boolean handleFileUploads) {
this.handleFileUploads = handleFileUploads;
return this;
}
@Override
public BodyHandler setBodyLimit(long bodyLimit) {
this.bodyLimit = bodyLimit;
return this;
}
@Override
public BodyHandler setUploadsDirectory(String uploadsDirectory) {
this.uploadsDir = uploadsDirectory;
return this;
}
@Override
public BodyHandler setMergeFormAttributes(boolean mergeFormAttributes) {
this.mergeFormAttributes = mergeFormAttributes;
return this;
}
@Override
public BodyHandler setDeleteUploadedFilesOnEnd(boolean deleteUploadedFilesOnEnd) {
this.deleteUploadedFilesOnEnd = deleteUploadedFilesOnEnd;
return this;
}
@Override
public BodyHandler setPreallocateBodyBuffer(boolean isPreallocateBodyBuffer) {
this.isPreallocateBodyBuffer = isPreallocateBodyBuffer;
return this;
}
private long parseContentLengthHeader(HttpServerRequest request) {
String contentLength = request.getHeader(HttpHeaders.CONTENT_LENGTH);
if (contentLength == null || contentLength.isEmpty()) {
return -1;
}
try {
long parsedContentLength = Long.parseLong(contentLength);
return parsedContentLength < 0 ? -1 : parsedContentLength;
} catch (NumberFormatException ex) {
return -1;
}
}
private class BHandler implements Handler<Buffer> {
private static final int MAX_PREALLOCATED_BODY_BUFFER_BYTES = 65535;
final RoutingContext context;
final long contentLength;
Buffer body;
boolean failed;
final AtomicInteger uploadCount = new AtomicInteger();
boolean ended;
long uploadSize = 0L;
final boolean isMultipart;
final boolean isUrlEncoded;
public BHandler(RoutingContext context, long contentLength) {
this.context = context;
this.contentLength = contentLength;
// the request clearly states that there should
// be a body, so we respect the client and ensure
// that the body will not be null
if (contentLength != -1) {
initBodyBuffer();
}
List<FileUpload> fileUploads = context.fileUploads();
final String contentType = context.request().getHeader(HttpHeaders.CONTENT_TYPE);
if (contentType == null) {
isMultipart = false;
isUrlEncoded = false;
} else {
final String lowerCaseContentType = contentType.toLowerCase();
isMultipart = lowerCaseContentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString());
isUrlEncoded = lowerCaseContentType
.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString());
}
if (isMultipart || isUrlEncoded) {
context.request().setExpectMultipart(true);
if (handleFileUploads) {
makeUploadDir(context.vertx().fileSystem());
}
context.request().uploadHandler(upload -> {
if (bodyLimit != -1 && upload.isSizeAvailable()) {
// we can try to abort even before the upload starts
long size = uploadSize + upload.size();
if (size > bodyLimit) {
failed = true;
context.cancelAndCleanupFileUploads();
context.fail(413);
return;
}
}
if (handleFileUploads) {
// we actually upload to a file with a generated filename
uploadCount.incrementAndGet();
String uploadedFileName = new File(uploadsDir, UUID.randomUUID().toString()).getPath();
FileUploadImpl fileUpload = new FileUploadImpl(context.vertx().fileSystem(), uploadedFileName,
upload);
fileUploads.add(fileUpload);
Future<Void> fut = upload.streamToFileSystem(uploadedFileName);
fut.onComplete(ar -> {
if (fut.succeeded()) {
uploadEnded();
} else {
context.cancelAndCleanupFileUploads();
context.fail(ar.cause());
}
});
}
});
}
context.request().exceptionHandler(t -> {
context.cancelAndCleanupFileUploads();
int sc = 200;
if (t instanceof DecoderException) {
// bad request
sc = 400;
if (t.getCause() != null) {
t = t.getCause();
}
}
context.fail(sc, t);
});
}
private void initBodyBuffer() {
int initialBodyBufferSize;
if (contentLength < 0) {
initialBodyBufferSize = DEFAULT_INITIAL_BODY_BUFFER_SIZE;
} else if (contentLength > MAX_PREALLOCATED_BODY_BUFFER_BYTES) {
initialBodyBufferSize = MAX_PREALLOCATED_BODY_BUFFER_BYTES;
} else {
initialBodyBufferSize = (int) contentLength;
}
if (bodyLimit != -1) {
initialBodyBufferSize = (int) Math.min(initialBodyBufferSize, bodyLimit);
}
this.body = Buffer.buffer(initialBodyBufferSize);
}
private void makeUploadDir(FileSystem fileSystem) {
if (!fileSystem.existsBlocking(uploadsDir)) {
fileSystem.mkdirsBlocking(uploadsDir);
}
}
@Override
public void handle(Buffer buff) {
if (failed) {
return;
}
uploadSize += buff.length();
if (bodyLimit != -1 && uploadSize > bodyLimit) {
failed = true;
context.cancelAndCleanupFileUploads();
context.fail(413);
} else {
// multipart requests will not end up in the request body
// url encoded should also not, however jQuery by default
// post in urlencoded even if the payload is something else
if (!isMultipart /* && !isUrlEncoded */) {
if (body == null) {
initBodyBuffer();
}
body.appendBuffer(buff);
}
}
}
void uploadEnded() {
int count = uploadCount.decrementAndGet();
// only if parsing is done and count is 0 then all files have been processed
if (ended && count == 0) {
doEnd();
}
}
void end(Void v) {
// this marks the end of body parsing, calling doEnd should
// only be possible from this moment onwards
ended = true;
// only if parsing is done and count is 0 then all files have been processed
if (uploadCount.get() == 0) {
doEnd();
}
}
void doEnd() {
if (failed || context.failed()) {
context.cancelAndCleanupFileUploads();
return;
}
if (deleteUploadedFilesOnEnd) {
context.addBodyEndHandler(x -> context.cancelAndCleanupFileUploads());
}
HttpServerRequest req = context.request();
if (mergeFormAttributes && req.isExpectMultipart()) {
req.params().addAll(req.formAttributes());
}
((RoutingContextInternal) context).setBody(body);
// release body as it may take lots of memory
body = null;
context.next();
}
}
}

View File

@ -0,0 +1,28 @@
package com.sf.vertx.handle;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import io.vertx.httpproxy.HttpProxy;
/**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/
@VertxGen
public interface ProxyHandler extends Handler<RoutingContext> {
static ProxyHandler create(WebClient mainWebClient, HttpProxy httpProxy) {
return new ProxyHandlerImpl(mainWebClient, httpProxy);
}
static ProxyHandler create(HttpProxy httpProxy) {
return new ProxyHandlerImpl(httpProxy);
}
static ProxyHandler create(HttpProxy httpProxy, int port, String host) {
return new ProxyHandlerImpl(httpProxy, port, host);
}
}

View File

@ -0,0 +1,62 @@
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;
private WebClient mainWebClient;
public ProxyHandlerImpl(WebClient mainWebClient, HttpProxy httpProxy) {
this.httpProxy = httpProxy;
this.mainWebClient = mainWebClient;
}
public ProxyHandlerImpl(HttpProxy httpProxy) {
this.httpProxy = httpProxy;
}
public ProxyHandlerImpl(HttpProxy httpProxy, int port, String host) {
this.httpProxy = httpProxy.origin(port, host);
}
@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);
// }
// });
// 原始代码只有如下一句
httpProxy.handle(ctx.request());
}
}

View File

@ -1,23 +0,0 @@
package com.sf.vertx.handle;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import io.vertx.httpproxy.HttpProxy;
/**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/
@VertxGen
public interface SFProxyHandler extends Handler<RoutingContext> {
static SFProxyHandler create(HttpProxy httpProxy) {
return new SFProxyHandlerImpl(httpProxy);
}
static SFProxyHandler create(HttpProxy httpProxy, int port, String host) {
return new SFProxyHandlerImpl(httpProxy, port, host);
}
}

View File

@ -1,40 +0,0 @@
package com.sf.vertx.handle;
import io.vertx.ext.web.RoutingContext;
import io.vertx.httpproxy.HttpProxy;
import lombok.extern.slf4j.Slf4j;
/**
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
*/
@Slf4j
public class SFProxyHandlerImpl implements SFProxyHandler {
private final HttpProxy httpProxy;
private RoutingContext ctx;
public SFProxyHandlerImpl(HttpProxy httpProxy) {
this.httpProxy = httpProxy;
}
public SFProxyHandlerImpl(HttpProxy httpProxy, int port, String host) {
this.httpProxy = httpProxy.origin(port, host);
}
@Override
public void handle(RoutingContext ctx) {
// String body = ctx.getBodyAsString();
// log.info("ctx.getBodyAsJson():{}", body);
this.ctx = ctx;
httpProxy.handle(ctx.request());
}
public RoutingContext getCtx() {
return ctx;
}
public void setCtx(RoutingContext ctx) {
this.ctx = ctx;
}
}

View File

@ -2,7 +2,9 @@ package com.sf.vertx.init;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
@ -11,23 +13,33 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.sf.vertx.api.pojo.AppConfig;
import com.sf.vertx.api.pojo.GatewayInterface;
import com.sf.vertx.api.pojo.Node;
import com.sf.vertx.api.pojo.SacService;
import com.sf.vertx.api.pojo.VertxConfig;
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
import com.sf.vertx.arithmetic.roundRobin.WeightedRoundRobin;
import com.sf.vertx.constans.RedisConfig;
import com.sf.vertx.handle.BodyHandler;
import com.sf.vertx.handle.ProxyHandler;
import com.sf.vertx.security.MainSecurity;
import com.sf.vertx.service.AppConfigService;
import com.sf.vertx.service.impl.AppConfigServiceImpl;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.proxy.handler.ProxyHandler;
import io.vertx.ext.web.client.WebClient;
import io.vertx.httpproxy.HttpProxy;
import io.vertx.httpproxy.ProxyContext;
import io.vertx.httpproxy.ProxyInterceptor;
import io.vertx.httpproxy.ProxyResponse;
import lombok.extern.slf4j.Slf4j;
/***
@ -40,8 +52,10 @@ import lombok.extern.slf4j.Slf4j;
@Order(value = 10)
@Component
public class DynamicBuildServer implements ApplicationRunner {
public static final String SAC_APP_HEADER_KEY = "sacAppCode";
private static ConcurrentHashMap<Integer, SacLoadBalancing> SAC_LOADBALANCING_MAP = new ConcurrentHashMap<Integer, SacLoadBalancing>();
@Value("${server.vertx.server.default.port}")
private Integer serverDefaultPort;
private Integer serverDefaultPort;
@Autowired
private AppConfigService appConfigService;
@ -53,7 +67,6 @@ public class DynamicBuildServer implements ApplicationRunner {
public void run(ApplicationArguments args) throws Exception {
// 初始化redis key
redisConfig.init();
// 加载vertx应用配置
appStartLoadData();
}
@ -62,13 +75,17 @@ public class DynamicBuildServer implements ApplicationRunner {
* 应用启动, 从redis读取配置,初始化vertx服务
*/
private void appStartLoadData() {
// 编解码线程池
// TODO 编解码线程池,后面优化协程等方式
Vertx VERTX = Vertx.vertx(new VertxOptions().setWorkerPoolSize(20));
// 创建HTTP监听
HttpServer server = VERTX.createHttpServer();
// 所有ip都能访问
HttpServerOptions httpServerOptions = new HttpServerOptions().setHost("0.0.0.0");
HttpServer server = VERTX.createHttpServer(httpServerOptions);
Router mainHttpRouter = Router.router(VERTX);
VertxConfig vertxConfig = appConfigService.loadVertxConfig();
Integer serverPort = (vertxConfig == null || vertxConfig.getPort() == null) ? serverDefaultPort : vertxConfig.getPort();
Integer serverPort = (vertxConfig == null || vertxConfig.getPort() == null) ? serverDefaultPort
: vertxConfig.getPort();
log.info("serverPort:{}", serverPort);
server.requestHandler(mainHttpRouter).listen(serverPort, h -> {
if (h.succeeded()) {
log.info("HTTP端口监听成功:{}", serverPort);
@ -78,50 +95,168 @@ public class DynamicBuildServer implements ApplicationRunner {
});
ConcurrentHashMap<String, AppConfig> cacheAppConfig = appConfigService.loadAllConfig();
for (String appCode : cacheAppConfig.keySet()) {
AppConfig appConfig = cacheAppConfig.get(appCode);
// 负载均衡算法
// 轮训
SacLoadBalancing sacLoadBalancing = roundRobin(appConfig.getNodeList());
// HttpClientOptions clientOptions = new HttpClientOptions();
// clientOptions.setMaxPoolSize(20); // 最大连接池大小
// clientOptions.setConnectTimeout(5000); // 连接超时 毫秒
// clientOptions.setHttp2KeepAliveTimeout(1);
// clientOptions.setIdleTimeout(1000); // 连接空闲超时 毫秒
// HttpClient proxyClient = VERTX.createHttpClient(clientOptions);
HttpClient proxyClient = VERTX.createHttpClient();
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(cacheAppConfig, request)));
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
// 修改uri context.request().setURI();
String sacAppHeaderKey = context.request().headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
log.info("addInterceptor uri appCode:{}", sacAppHeaderKey);
// 判断是否需要加解析
if (AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
//String data = decode(null, sacAppHeaderKey);
// 有策略
if (appConfig.getStrategyList() != null && appConfig.getStrategyList().size() > 0) {
} else {
// 无策略,直接走反向代理
// HttpClientOptions clientOptions = new HttpClientOptions();
// clientOptions.setMaxPoolSize(20); // 最大连接池大小
// clientOptions.setConnectTimeout(5000); // 连接超时 毫秒
// clientOptions.setHttp2KeepAliveTimeout(1);
// clientOptions.setIdleTimeout(1000); // 连接空闲超时 毫秒
// HttpClient proxyClient = VERTX.createHttpClient(clientOptions);
HttpClient proxyClient = VERTX.createHttpClient();
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(sacLoadBalancing, request)));
mainHttpRouter.route().handler(ProxyHandler.create(proxy));
}
return context.sendRequest();
}
});
// mainHttpRouter.route().handler(ProxyHandler.create(proxy));
WebClient mainWebClient = WebClient.create(VERTX);
mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy));
}
public SocketAddress resolveOriginAddress(ConcurrentHashMap<String, AppConfig> cacheAppConfig,
HttpServerRequest request) {
String appCode = request.getHeader(SAC_APP_HEADER_KEY);
log.info("uri appCode:{}", appCode);
// TODO 不存在, 抛异常给前端
AppConfig appConfig = cacheAppConfig.get(appCode);
if (appConfig != null) {
Integer environmentId = null;
SacLoadBalancing sacLoadBalancing = null;
// 2是否存在server服务配置
if (appConfig.getService() != null && appConfig.getService().size() > 0) {
for (SacService service : appConfig.getService()) {
// uri是否匹配
boolean match = false;
for (GatewayInterface gatewayInterface : service.getUriList()) {
String domain = request.authority().port() == -1 ? request.authority().host()
: request.authority().host() + ":" + request.authority().port();
// uri匹配(正则或全量匹配)
match = gatewayInterface.isUriRegular() ? regexMatch(gatewayInterface.getUri(), request.uri())
: StringUtils.equals(gatewayInterface.getUri(), request.uri());
match = match ? StringUtils.equals(gatewayInterface.getMethod(), request.method().name())
: false;
// domain匹配
if (match) {
boolean domainVertify = service.getRouter() != null
&& StringUtils.isNotBlank(service.getRouter().getDomain())
&& StringUtils.equals(service.getRouter().getDomain(), domain) ? true : false;
if (domainVertify) {
environmentId = service.getRouter().getEnvironmentId();
} else {
environmentId = appConfig.getEnvironmentConfig().getDefaultId();
}
break;
}
}
}
} else {
environmentId = appConfig.getEnvironmentConfig().getDefaultId();
}
// 初始化负载均衡
if (SAC_LOADBALANCING_MAP.get(environmentId) != null) {
sacLoadBalancing = SAC_LOADBALANCING_MAP.get(environmentId);
} else {
// 并发影响不大, 只要初始化成功一次即可
List<Node> nodeList = appConfig.getEnvironmentConfig().getEnvironmentGroup().get(environmentId);
if (nodeList != null && nodeList.size() > 0) {
// 初始化负载均衡算法
sacLoadBalancing = roundRobin(nodeList);
SAC_LOADBALANCING_MAP.put(environmentId, sacLoadBalancing);
} else {
// TODO 抛出异常
}
}
// TODO 区分httpshttp
Node node = sacLoadBalancing.selectNode();
SocketAddress socketAddress = SocketAddress.inetSocketAddress(node.getPort(), node.getIp());
log.info("负载均衡跳转地址:{}", socketAddress.host() + ";" + socketAddress.port());
return socketAddress;
}
// TODO 如果没找到, 如何处理
return null;
}
public SocketAddress resolveOriginAddress(SacLoadBalancing sacLoadBalancing, HttpServerRequest request) {
// TODO 区分httpshttp
Node node = sacLoadBalancing.selectNode();
SocketAddress socketAddress = SocketAddress.inetSocketAddress(node.getPort(), node.getIp());
log.info("负载均衡跳转地址:{}", socketAddress.host() +";"+socketAddress.port());
return socketAddress;
// private String getUriAppCode(String uri) {
// // 1判断appCode
// int count = 0;
// int appCodeLen = 16;
// StringBuffer appCode = new StringBuffer();
// for (int i = 0; i < uri.length(); i++) {
// // 限制长度
// if (appCode.length() == appCodeLen) {
// break;
// }
// char ch = uri.charAt(i);
// if (ch == '/') {
// if (++count == 2) {
// break;
// }
// } else {
// appCode.append(ch);
// }
// }
// return appCode.toString();
// }
private static boolean regexMatch(String pattern, String target) {
return Pattern.matches(pattern, target);
}
/***
* 解密
*/
private static String decode(AppConfig appConfig, String bodyJson) {
String algorithm = appConfig.getDataSecurity().getAlgorithm();
String data = null;
switch (algorithm) {
case "aes":
// 解密
String key = appConfig.getDataSecurity().getKey();
data = MainSecurity.aesDecrypt(bodyJson, key);
break;
default:
break;
}
return data;
}
private SacLoadBalancing roundRobin(List<Node> nodeList) {
/***
* 加密
*/
private static String encryption(AppConfig appConfig, String responseData) {
String algorithm = appConfig.getDataSecurity().getAlgorithm();
String data = null;
switch (algorithm) {
case "aes":
String key = appConfig.getDataSecurity().getKey();
data = MainSecurity.aesEncrypt(responseData.toString(), key);
break;
default:
break;
}
return data;
}
private static SacLoadBalancing roundRobin(List<Node> nodeList) {
WeightedRoundRobin weightedRoundRobin = new WeightedRoundRobin();
int weight = 1;
for(Node node : nodeList) {
for (Node node : nodeList) {
int weight = node.getWeight() != null ? node.getWeight() : 1;
node.setWeight(weight);
node.setCurrentWeight(weight);
node.setEffectiveWeight(weight);
weight++;
WeightedRoundRobin.totalWeight += node.getEffectiveWeight();
}
weightedRoundRobin.init(nodeList);

View File

@ -8,7 +8,7 @@ import com.sf.vertx.api.pojo.VertxConfig;
public interface AppConfigService {
ConcurrentHashMap<String, AppConfig> loadAllConfig();
void addAppConfig(AppConfig appConfig);
void addAppConfig(String appConfig);
void deleteAppConfig(AppConfig appConfig);

View File

@ -1,6 +1,5 @@
package com.sf.vertx.service.impl;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -12,8 +11,8 @@ import org.springframework.stereotype.Service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.sf.vertx.api.pojo.AppConfig;
import com.sf.vertx.api.pojo.Node;
import com.sf.vertx.api.pojo.VertxConfig;
import com.sf.vertx.constans.RedisConfig;
import com.sf.vertx.service.AppConfigService;
@ -27,26 +26,24 @@ public class AppConfigServiceImpl implements AppConfigService {
private String vertxEnvironment;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final ConcurrentHashMap<String, AppConfig> cacheAppConfig = new ConcurrentHashMap<>();
public static boolean appDataSecurity(String appCode) {
return cacheAppConfig.get(appCode) != null && cacheAppConfig.get(appCode).getDataSecurity() != null ? true : false;
}
/***
* 从redis加载数据
*/
public ConcurrentHashMap<String, AppConfig> loadAllConfig() {
ConcurrentHashMap<String, AppConfig> cacheAppConfig = new ConcurrentHashMap<>();
// 获取所有app列表
Set<String> set = redisTemplate.opsForZSet().range(RedisConfig.APP_CONFIG_SET_KEY, 0, -1);
for(String appCode : set) {
String appCodeKey = RedisConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
String appCodeValue = redisTemplate.opsForValue().get(appCodeKey);
if(StringUtils.isNotBlank(appCodeValue)) {
AppConfig appConfig = JSON.parseObject(appCodeValue, AppConfig.class);
AppConfig appConfig = JSON.parseObject(appCodeValue, new TypeReference<AppConfig>() {});
cacheAppConfig.put(appCode, appConfig);
//JSONObject jsonObject = JSONObject.parseObject(appCodeValue);//从请求体里获得jsonObject
//String oldGoodsStorageModes = jsonObject.getString("nodeList");//解析成字符串
//字符串转list
//List<Node> oldGoodsStoragemodes = JSON.parseArray(oldGoodsStorageModes,Node.class);
//log.info("oldGoodsStoragemodes:{}", JSON.toJSONString(oldGoodsStoragemodes));
}
}
log.info("cacheAppConfig:{}", JSON.toJSONString(cacheAppConfig));
@ -67,10 +64,11 @@ public class AppConfigServiceImpl implements AppConfigService {
* 新增修改
* @param appConfig
*/
public void addAppConfig(AppConfig appConfig) {
public void addAppConfig(String appConfigStr) {
AppConfig appConfig = JSON.parseObject(appConfigStr, AppConfig.class);
redisTemplate.opsForZSet().add(RedisConfig.APP_CONFIG_SET_KEY, appConfig.getAppCode(), 0);
String appCodeKey = RedisConfig.APP_CONFIG_PREFIX_KEY + ":" + appConfig.getAppCode();
redisTemplate.opsForValue().set(appCodeKey, JSONObject.toJSONString(appConfig));
redisTemplate.opsForValue().set(appCodeKey, appConfigStr);
// 发送redis队列,vertx处理
//String queue = RedisConfig.VETX_ENVIRONMENT_KEY+vertxEnvironment+":list";

View File

@ -4,7 +4,7 @@ server:
environment: dev
server:
default:
port: 9099
port: 80
# 服务器的HTTP端口默认为8080
port: 5566
servlet:
@ -38,9 +38,9 @@ spring:
# redis 配置
redis:
# 地址
host: 192.168.1.23
host: 127.0.0.1 #192.168.1.23
# 端口默认为6379
port: 22002
port: 6379 #22002
# 数据库索引
database: 0
# 密码

View File

@ -0,0 +1,24 @@
package com.sf.vertx;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
public class PatternTest {
@Test
public void match() {
String target = "/test/a.html";
String pattern = "^/test/.*";
// String target = "hello, world";
// String pattern = "^hello.* ";
boolean isMatch = Pattern.matches(pattern, target);
log.info("result:{}", isMatch);
}
}

View File

@ -5,8 +5,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import com.alibaba.fastjson2.JSONObject;
import com.sf.vertx.handle.SFProxyHandler;
import com.sf.vertx.handle.SFProxyHandlerImpl;
import com.sf.vertx.handle.ProxyHandlerImpl;
import com.sf.vertx.security.MainSecurity;
import io.vertx.core.AsyncResult;
@ -80,45 +79,45 @@ public class TestProxy {
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(request)));
SFProxyHandler sfProxyHandler = SFProxyHandler.create(proxy);
ProxyHandler sfProxyHandler = ProxyHandler.create(proxy);
WebClient mainWebClient = WebClient.create(VERTX);
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
SFProxyHandlerImpl sfProxyHandlerImpl = (SFProxyHandlerImpl) sfProxyHandler;
// @Override
// public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
// ProxyHandlerImpl sfProxyHandlerImpl = (ProxyHandlerImpl) sfProxyHandler;
// String body = sfProxyHandlerImpl.getCtx().getBodyAsString();
// log.info("ctx.getBodyAsJson():{}", body);
JsonObject bodyJson = sfProxyHandlerImpl.getCtx().getBodyAsJson();
HttpServerRequest HttpServerRequest = context.request().proxiedRequest();
//JsonObject bodyJson = sfProxyHandlerImpl.getCtx().getBodyAsJson();
//HttpServerRequest HttpServerRequest = context.request().proxiedRequest();
// 解密
String data = MainSecurity.aesDecrypt(bodyJson.getString("data"), "dadddsdfadfadsfa33323223");
bodyJson.put("data", data);
//String data = MainSecurity.aesDecrypt(bodyJson.getString("data"), "dadddsdfadfadsfa33323223");
//bodyJson.put("data", data);
// end解密
// uri重写
// context.request().setURI("/vertx/body");
return Future.future(p -> {
mainWebClient.postAbs("http://localhost:9198/vertx/body").sendJson(bodyJson.toString(), h -> {
if (h.succeeded()) {
ProxyRequest proxyRequest = context.request();
// 释放资源
proxyRequest.release();
JsonObject responseData = h.result().bodyAsJsonObject();
// responseData.toBuffer()
// 加密
String dataStr = MainSecurity.aesEncrypt(responseData.toString(),
"dadddsdfadfadsfa33323223");
log.info("dataStr:{}", dataStr);
Buffer buffer = Buffer.buffer(dataStr);
// end 加密
ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
.putHeader("content-type", "application/json").setBody(Body.body(buffer));
p.complete(proxyResponse);
} else {
p.fail(h.cause());
}
});
});
}
// return Future.future(p -> {
// mainWebClient.postAbs("http://localhost:9198/vertx/body").sendJson(bodyJson.toString(), h -> {
// if (h.succeeded()) {
// ProxyRequest proxyRequest = context.request();
// // 释放资源
// proxyRequest.release();
// JsonObject responseData = h.result().bodyAsJsonObject();
// // responseData.toBuffer()
// // 加密
// String dataStr = MainSecurity.aesEncrypt(responseData.toString(),
// "dadddsdfadfadsfa33323223");
// log.info("dataStr:{}", dataStr);
// Buffer buffer = Buffer.buffer(dataStr);
// // end 加密
// ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
// .putHeader("content-type", "application/json").setBody(Body.body(buffer));
// p.complete(proxyResponse);
// } else {
// p.fail(h.cause());
// }
// });
// });
// }
});
// mainHttpRouter.route().handler(sfProxyHandler);
mainHttpRouter.route().handler(BodyHandler.create()).handler(sfProxyHandler);
@ -173,18 +172,18 @@ public class TestProxy {
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(request)));
// mock数据
// proxy.addInterceptor(new ProxyInterceptor() {
// @Override
// public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
// ProxyRequest proxyRequest = context.request();
// // 释放资源
// proxyRequest.release();
// // 创建一个响应并设置参数
// ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
// .putHeader("content-type", "text/plain").setBody(Body.body(Buffer.buffer("Hello World")));
// return Future.succeededFuture(proxyResponse);
// }
// });
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
// 释放资源
proxyRequest.release();
// 创建一个响应并设置参数
ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
.putHeader("content-type", "text/plain").setBody(Body.body(Buffer.buffer("Hello World")));
return Future.succeededFuture(proxyResponse);
}
});
proxy.addInterceptor(new ProxyInterceptor() {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
@ -238,7 +237,7 @@ public class TestProxy {
return context.sendResponse();
}
});
// mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(proxy));
mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(proxy));
mainHttpRouter.route().handler(ProxyHandler.create(proxy));
// mainHttpRouter.route().blockingHandler(ProxyHandler.create(proxy));
// mainHttpRouter.routeWithRegex("\\/vertx/*").handler(ProxyHandler.create(proxy));