change: 各位基友强烈要求把nutzcloud的代码分开放

This commit is contained in:
Wendal Chen 2018-03-30 11:37:00 +08:00
parent fa6f163851
commit df91caa94e
58 changed files with 1 additions and 2320 deletions

View File

@ -10,10 +10,6 @@
* add: 添加单元测试的支持
* add: shiro主动搜索ioc容器内的SessionListener并添加到SessionManager
* add: jetty添加staticPathLocal属性,解决idea用户开发期js/css文件不更新的问题
* add: loach-server NC的'泥鳅'服务端,服务注册与发现
* add: loach-client NC的'泥鳅'客户端,服务注册与发现
* add: literpc NC的简洁高效的RPC服务
* add: gateway-server NC的网关服务
# 2.1.4 "平凡之路"

View File

@ -159,7 +159,7 @@ public class MainLauncher {
- [x] starter-config-client NB Config Client 配置中心的客户端
- [x] starter-[apollo-client](https://github.com/ctripcorp/apollo) 携程框架部门研发的分布式配置中心的客户端
- API网关
- [x] gateway-server NB API网关服务器
- [x] gateway-server NC API网关服务器
- [ ] zuul
- 数据库类相关
- 关系型数据库

View File

@ -1,23 +0,0 @@
# NutzCloud
## 模块划分
- [x] gateway-server API网关
- [x] loach-server 服务注册与服务发现的服务端
- [x] loach-client 服务注册与服务发现的客户端
- [x] literpc 简洁高效的RPC服务
- [x] cloud-demo 演示组件间的协作
## 编译说明
### 独立使用的组件
对应`mvn -Pstandalone`
- gateway-server
- loach-server
他们均自带Launcher, 可直接启动
### demo
对应`mvn -Pdemo`

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-demo-common</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.nutz.cloud</groupId>
<artifactId>literpc</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -1,46 +0,0 @@
package io.nutz.cloud.demo.bean;
import java.io.Serializable;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
@Table("t_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private int id;
@Name
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

View File

@ -1,18 +0,0 @@
package io.nutz.cloud.demo.service;
import java.util.List;
import org.nutz.boot.starter.literpc.api.RpcService;
import io.nutz.cloud.demo.bean.User;
public interface UserService extends RpcService {
List<User> list();
User fetch(int id);
User add(String name, int age);
void delete(int id);
}

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-demo-service</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-nutz-dao</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo-common</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.nutz.cloud</groupId>
<artifactId>loach-client</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/nutz/org.nutz.boot.starter.NbStarter</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.nutz.demo.simple.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,33 +0,0 @@
package io.nutz.cloud.demo;
import org.nutz.boot.NbApp;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import io.nutz.cloud.demo.bean.User;
@IocBean(create = "init")
public class CloudServiceLauncher {
@Inject
protected Dao dao;
@Ok("raw")
@At("/Status")
public NutMap status() {
return new NutMap();
}
public void init() {
dao.create(User.class, false);
}
public static void main(String[] args) throws Exception {
new NbApp().setPrintProcDoc(true).run();
}
}

View File

@ -1,39 +0,0 @@
package io.nutz.cloud.demo.service.impl;
import java.util.List;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.Param;
import io.nutz.cloud.demo.bean.User;
import io.nutz.cloud.demo.service.UserService;
@IocBean
public class UserServiceImpl implements UserService {
@Inject
protected Dao dao;
public List<User> list() {
return dao.query(User.class, null);
}
public User fetch(@Param("id")int id) {
return dao.fetch(User.class, id);
}
public User add(@Param("name")String name, @Param("age")int age) {
User user = new User();
user.setName(name);
user.setAge(age);
return dao.insert(user);
}
public void delete(@Param("id")int id) {
dao.clear(User.class, Cnd.where("id", "=", id));
}
}

View File

@ -1,5 +0,0 @@
nutz.application.name=user-service
server.port=0
jdbc.url=jdbc:mysql://127.0.0.1:3306/nbdemo
jdbc.username=root
jdbc.password=root

View File

@ -1,7 +0,0 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, So NB!</title>
</head>
<body>
<div>
<h2>这是服务端,不带任何feign依赖及代码.</h2>
</div>
<div>
<h2>
请启动并访问客户端 <a href="http://127.0.0.1:8082">http://127.0.0.1:8082</a>
</h2>
</div>
</body>
</html>

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-demo-web</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-nutz-mvc</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.nutz.cloud</groupId>
<artifactId>loach-client</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo-common</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/nutz/org.nutz.boot.starter.NbStarter</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.nutz.demo.simple.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,14 +0,0 @@
package io.nutz.cloud.demo;
import org.nutz.boot.NbApp;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
public class CloudWebLauncher {
// 端口是8082, 请先启动 loach-server
public static void main(String[] args) throws Exception {
new NbApp().setPrintProcDoc(true).run();
}
}

View File

@ -1,74 +0,0 @@
package io.nutz.cloud.demo.module;
import java.util.List;
import org.nutz.boot.starter.literpc.annotation.RpcInject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.adaptor.JsonAdaptor;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.DELETE;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST;
import org.nutz.mvc.annotation.Param;
import io.nutz.cloud.demo.bean.User;
import io.nutz.cloud.demo.service.UserService;
@IocBean
@At("/user")
@Ok("json:full")
@AdaptBy(type=JsonAdaptor.class)
public class UserModule {
private static final Log log = Logs.get();
@RpcInject
protected UserService userService;
@At
public List<User> list() {
return userService.list();
}
@At
public User fetch(@Param("id")int id) {
return userService.fetch(id);
}
@POST
public User add(@Param("name")String name, @Param("age")int age) {
return userService.add(name, age);
}
@DELETE
public void delete(@Param("id")int id) {
userService.delete(id);
}
/**
* 这是演示api调用的入口,会顺序调用一堆请求,请关注日志
*/
@Ok("raw")
@At
public String apitest() {
List<User> users = userService.list();
log.info("users=" + Json.toJson(users));
User haoqoo = userService.add("haoqoo", 19);
User wendal = userService.add("wendal", 28);
users = userService.list();
log.info("users=" + Json.toJson(users));
userService.delete(haoqoo.getId());
userService.delete(wendal.getId());
users = userService.list();
log.info("users=" + Json.toJson(users));
return "done";
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}

View File

@ -1,3 +0,0 @@
nutz.application.name=web
server.port=8082
server.host=0.0.0.0

View File

@ -1,10 +0,0 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.logger.org.apache.http=info
log4j.logger.com.netflix.discovery=info
log4j.logger.com.netflix.loadbalancer=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, So NB!</title>
</head>
<body>
<div>
<h2>这是Feign客户端,没有dao相关的依赖及代码</h2>
</div>
<div>
<h2>
<a href="user/apitest">调用apitest,请看日志的输出</a>
</h2>
</div>
</body>
</html>

View File

@ -1,12 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-cloud</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<groupId>io.nutz.cloud.demo</groupId>
<artifactId>cloud-demo</artifactId>
<packaging>pom</packaging>
</project>

View File

@ -1,31 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<artifactId>gateway-server</artifactId>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-proxy</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.nutz.cloud</groupId>
<artifactId>loach-client</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jetty</artifactId>
</dependency>
</dependencies>
<groupId>org.nutz.cloud</groupId>
</project>

View File

@ -1,81 +0,0 @@
package org.nutz.boot.starter.gateway.server;
import java.io.IOException;
import java.util.Iterator;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.proxy.AsyncMiddleManServlet;
import org.nutz.boot.starter.WebServletFace;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
@SuppressWarnings("serial")
public class ApiGateWayServlet extends AsyncMiddleManServlet implements WebServletFace {
public static final String NAME_ROUTE_CONCEXT = "gateway.route_context";
@Inject
protected RouteConfig routeConfig;
@Override
protected void service(HttpServletRequest clientRequest, HttpServletResponse proxyResponse) throws ServletException, IOException {
RouteContext ctx = new RouteContext();
ctx.setup(clientRequest, proxyResponse);
clientRequest.setAttribute(NAME_ROUTE_CONCEXT, ctx);
Iterator<RouteFilter> it = routeConfig.getRouteFilters();
while (it.hasNext()) {
if (!it.next().preRoute(ctx)) {
return;
}
}
if (ctx.targetHost == null && ctx.rewritedTarget == null) {
proxyResponse.sendError(404);
return;
}
super.service(clientRequest, proxyResponse);
}
@Override
protected String rewriteTarget(HttpServletRequest clientRequest) {
RouteContext ctx = ((RouteContext)clientRequest.getAttribute(NAME_ROUTE_CONCEXT));
if (ctx.rewritedTarget != null)
return ctx.rewritedTarget;
String url = ctx.targetHost;
if (url.startsWith("http://") || url.startsWith("https://")) {
}
else {
url = "http://" + url;
}
if (ctx.targetPort > 0) {
url += ":" + ctx.targetPort;
}
if (ctx.targetUri == null) {
url += ctx.uri;
}
else {
url += ctx.targetUri;
}
if (ctx.queryString != null) {
url += "?" + ctx.queryString;
}
return url;
}
public String getName() {
return "gateway";
}
public String getPathSpec() {
return "/*";
}
public Servlet getServlet() {
return this;
}
}

View File

@ -1,11 +0,0 @@
package org.nutz.boot.starter.gateway.server;
import org.nutz.boot.NbApp;
public class GatewayServerLauncher {
public static void main(String[] args) {
new NbApp().run();
}
}

View File

@ -1,44 +0,0 @@
package org.nutz.boot.starter.gateway.server;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.nutz.boot.starter.gateway.server.impl.SimpleRouteFilter;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.log.Log;
import org.nutz.log.Logs;
@IocBean(create="init")
public class RouteConfig {
private static final Log log = Logs.get();
@Inject
protected PropertiesProxy conf;
@Inject("refer:$ioc")
protected Ioc ioc;
protected List<RouteFilter> routes = new LinkedList<>();
public Iterator<RouteFilter> getRouteFilters() {
return routes.iterator();
}
public void init() {
for (String key : conf.getKeys()) {
if (key.startsWith("gw.") && key.endsWith(".filters")) {
String name = key.substring("gw.".length(), key.length() - ".filters".length());
log.debug("add config for name=" + name);
// 当前仅支持simple,直接new就行了
SimpleRouteFilter simple = new SimpleRouteFilter();
simple.setPropertiesProxy(ioc, conf, key.substring(0, key.length() - ".filters".length()));
routes.add(simple);
}
}
}
}

View File

@ -1,43 +0,0 @@
package org.nutz.boot.starter.gateway.server;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RouteContext {
public String method;
public String uri;
public String host;
public Map<String, String> headers;
public String queryString;
public String targetHost;
public String targetUri;
public int targetPort;
public String rewritedTarget;
public int connectTimeOut, sendTimeOut, readTimeOut;
public HttpServletResponse resp;
public HttpServletRequest req;
public void setup(HttpServletRequest req, HttpServletResponse resp) {
method = req.getMethod().toUpperCase();
uri = req.getRequestURI();
host = req.getHeader("Host");
headers = new HashMap<>();
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, req.getHeader(headerName));
}
queryString = req.getQueryString();
this.req = req;
this.resp = resp;
}
}

View File

@ -1,19 +0,0 @@
package org.nutz.boot.starter.gateway.server;
import java.io.IOException;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
public interface RouteFilter {
default boolean preRoute(RouteContext ctx) throws IOException {
return true;
}
default void postRoute(RouteContext ctx) throws IOException {}
default void setPropertiesProxy(Ioc ioc, PropertiesProxy conf, String prefix) {}
String nickname();
}

View File

@ -1,159 +0,0 @@
package org.nutz.boot.starter.gateway.server.impl;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
import org.nutz.boot.starter.gateway.server.RouteContext;
import org.nutz.boot.starter.gateway.server.RouteFilter;
import org.nutz.boot.starter.loach.client.LoachClient;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
public class SimpleRouteFilter implements RouteFilter {
private static final Log log = Logs.get();
protected String prefix;
protected String[] hostnames;
protected String serviceName;
protected String[] servers;
protected String[] uriPrefixs;
protected boolean removePrefix;
protected Pattern uriPattern;
protected int connectTimeOut, sendTimeOut, readTimeOut;
protected boolean corsEnable;
protected LoachClient loachClient;
public void setPropertiesProxy(Ioc ioc, PropertiesProxy conf, String prefix) {
// 需要匹配的域名
String hostnames = conf.get(prefix + ".hostnames");
if (!Strings.isBlank(hostnames)) {
this.hostnames = Strings.splitIgnoreBlank(hostnames, "(;|,)");
}
// 固定转发的服务器vip
String servers = conf.get(prefix + ".servers");
if (!Strings.isBlank(servers)) {
this.servers = Strings.splitIgnoreBlank(servers, "(;|,)");
}
else {
// 服务名
serviceName = conf.get(prefix + ".serviceName");
if (!Strings.isBlank(serviceName)) {
loachClient = ioc.get(LoachClient.class);
}
}
String uriPrefixs = conf.get(prefix + ".uri.prefixs");
if (!Strings.isBlank(uriPrefixs)) {
this.uriPrefixs = Strings.splitIgnoreBlank(uriPrefixs, "(;|,)");
}
removePrefix = conf.getBoolean(prefix + ".uri.prefix.remove", false);
String uripattern = conf.get(prefix + ".uri.match");
if (!Strings.isBlank(uripattern)) {
uriPattern = Pattern.compile(uripattern);
}
connectTimeOut = conf.getInt(prefix + ".time.connect", 2000);
sendTimeOut = conf.getInt(prefix + ".time.send", 3000);
readTimeOut = conf.getInt(prefix + ".time.read", 3000);
corsEnable = conf.getBoolean(prefix + ".cors.enable");
}
@Override
public boolean preRoute(RouteContext ctx) throws IOException {
// 校验Host
if (!checkHost(ctx))
return true;
// 校验uri前缀
if (!checkUriPrefix(ctx))
return true;
// 校验uri正则表达式
if (!checkUriPattern(ctx))
return true;
// 设置一些必要的超时设置
ctx.connectTimeOut = connectTimeOut;
ctx.sendTimeOut = sendTimeOut;
ctx.readTimeOut = readTimeOut;
// TODO 处理跨域
if (servers != null) {
ctx.targetHost = servers[R.random(0, servers.length)];
}
else {
List<NutMap> services = loachClient.getService(serviceName);
if (services.isEmpty()) {
log.debugf("emtry server list for [%s]", serviceName);
ctx.resp.sendError(500);
return false; // 终止匹配
}
else {
NutMap service = services.get(R.random(0, services.size() - 1));
ctx.targetHost = service.getString("vip");
ctx.targetPort = service.getInt("port");
}
}
return RouteFilter.super.preRoute(ctx);
}
public boolean checkHost(RouteContext ctx) {
if (hostnames == null)
return true;
boolean pass = false;
for (String hostname : hostnames) {
if (hostname.equals(ctx.host)) {
pass = true;
break;
}
}
return pass;
}
public boolean checkUriPrefix(RouteContext ctx) {
if (uriPrefixs == null)
return true;
boolean pass = false;
// 校验URL前缀
if (uriPrefixs != null) {
for (String prefix : uriPrefixs) {
if (ctx.uri.startsWith(prefix)) {
pass = true;
if (removePrefix) {
if (ctx.uri.length() == prefix.length()) {
ctx.targetUri = "/";
}
else {
ctx.targetUri = ctx.uri.substring(prefix.length());
}
}
}
}
}
return pass;
}
public boolean checkUriPattern(RouteContext ctx) {
if (uriPattern == null)
return true;
return uriPattern.matcher(ctx.uri).find();
}
public String nickname() {
return "simple";
}
}

View File

@ -1,8 +0,0 @@
server.port=8620
server.host=0.0.0.0
nutz.application.name=gateway
gw.demo.filters=simple
gw.demo.serviceName=demo
gw.demo.url.prefixs=/

View File

@ -1,7 +0,0 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -1,18 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-cloud</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<groupId>org.nutz.cloud</groupId>
<artifactId>literpc</artifactId>
<dependencies>
<dependency>
<groupId>org.nutz.cloud</groupId>
<artifactId>loach-client</artifactId>
<version>2.2-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -1,123 +0,0 @@
package org.nutz.boot.starter.literpc;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.boot.starter.literpc.impl.RpcInvoker;
import org.nutz.boot.starter.literpc.impl.endpoint.RpcEndpoint;
import org.nutz.boot.starter.loach.client.LoachClient;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
@IocBean(create="init")
public class LiteRpc implements LoachClient.UpdateListener {
private static final Log log = Logs.get();
protected static String RPC_REG_KEY = "literpc.v1";
@Inject("refer:$ioc")
protected Ioc ioc;
/**
* 持有所有的执行器
*/
protected Map<String, RpcInvoker> invokers = new ConcurrentHashMap<>();
/**
* 方法签名对应的可用服务器列表
*/
protected Map<String, List<NutMap>> services = new HashMap<>();
/**
* 受支持的序列化器
*/
protected Map<String, RpcSerializer> serializers = new HashMap<>();
/**
* 受支持的通信方式
*/
protected Map<String, RpcEndpoint> endpoints = new HashMap<>();
public void init() {
// 获取所有RpcSerializer实例,并注册
for(String name : ioc.getNamesByType(RpcSerializer.class)) {
registerSerializer(ioc.get(RpcSerializer.class, name));
}
// 获取所有RpcEndpoint实例,并注册
for(String name : ioc.getNamesByType(RpcEndpoint.class)) {
registerEndpoint(ioc.get(RpcEndpoint.class, name));
}
}
public RpcInvoker getInvoker(String methodSign) {
return invokers.get(methodSign);
}
public RpcInvoker registerInovker(String methodSign, RpcInvoker invoker) {
return invokers.put(methodSign, invoker);
}
public static String getMethodSign(Method method) {
return Lang.sha1(method.toGenericString());
}
public void updateLoachRegInfo() {
LoachClient.EXT_REG_DATA.put(RPC_REG_KEY, new ArrayList<>(invokers.keySet()));
}
public List<NutMap> getServers(String methodSign) {
return this.services.get(methodSign);
}
public void onUpdate(Map<String, List<NutMap>> services) {
if (services == null || services.isEmpty()) {
return; // fuck
}
Map<String, List<NutMap>> rpcMap = new HashMap<>();
for (List<NutMap> _se : services.values()) {
for (NutMap server : _se) {
List<String> rpcKeys = server.getList(RPC_REG_KEY, String.class);
if (rpcKeys == null || rpcKeys.isEmpty())
continue;
for (String rpcKey : rpcKeys) {
List<NutMap> servers = rpcMap.get(rpcKey);
if (servers == null) {
servers = new ArrayList<>();
rpcMap.put(rpcKey, servers);
}
servers.add(server);
}
}
}
this.services = rpcMap;
}
public RpcSerializer getSerializer(String name) {
return serializers.get(name);
}
public void registerSerializer(RpcSerializer serializer) {
log.debug("add RpcSerializer name=" + serializer.getName());
this.serializers.put(serializer.getName(), serializer);
}
public RpcEndpoint getEndpoint(String name) {
return endpoints.get(name);
}
public void registerEndpoint(RpcEndpoint endpoint) {
log.debug("add RpcEndpoint name=" + endpoint.getName());
this.endpoints.put(endpoint.getName(), endpoint);
}
}

View File

@ -1,25 +0,0 @@
package org.nutz.boot.starter.literpc;
@SuppressWarnings("serial")
public class RpcException extends RuntimeException {
public RpcException() {
}
public RpcException(String message) {
super(message);
}
public RpcException(Throwable cause) {
super(cause);
}
public RpcException(String message, Throwable cause) {
super(message, cause);
}
public RpcException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -1,25 +0,0 @@
package org.nutz.boot.starter.literpc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.nutz.boot.starter.literpc.impl.proxy.DefaultRpcInjectProxy;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface RpcInject {
Class<? extends DefaultRpcInjectProxy> by() default DefaultRpcInjectProxy.class;
int connectTimeout() default -1;
int timeout() default -1;
String endpointType() default "";
String serializer() default "";
}

View File

@ -1,11 +0,0 @@
package org.nutz.boot.starter.literpc.api;
import java.lang.reflect.Method;
public class RpcReq {
public Object object;
public Method method;
public String methodSign;
public Object[] args;
public int timeout, connectTimeout;
}

View File

@ -1,22 +0,0 @@
package org.nutz.boot.starter.literpc.api;
import java.io.Serializable;
public class RpcResp implements Serializable {
private static final long serialVersionUID = 1L;
public Throwable err;
public Object returnValue;
public RpcResp() {
}
public RpcResp(Object returnValue) {
this.returnValue = returnValue;
}
public RpcResp(Throwable err) {
this.err = err;
}
}

View File

@ -1,13 +0,0 @@
package org.nutz.boot.starter.literpc.api;
import java.io.InputStream;
import java.io.OutputStream;
public interface RpcSerializer {
void write(Object obj, OutputStream out) throws Exception;
Object read(InputStream ins) throws Exception;
String getName();
}

View File

@ -1,4 +0,0 @@
package org.nutz.boot.starter.literpc.api;
public interface RpcService {
}

View File

@ -1,68 +0,0 @@
package org.nutz.boot.starter.literpc.impl;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import org.nutz.boot.starter.literpc.LiteRpc;
import org.nutz.boot.starter.literpc.annotation.RpcInject;
import org.nutz.boot.starter.literpc.impl.proxy.AbstractRpcRefProxy;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.IocEventListener;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Mirror;
/**
* 当一个ioc对象生成的时候,看看有无@RpcInject注解,如果有,注入代理对象
*
*/
@IocBean(depose="depose")
public class LiteRpcInjectFactory implements IocEventListener {
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected LiteRpc liteRpc;
protected Map<String, AbstractRpcRefProxy> rpcProxys = new HashMap<>();
protected ClassLoader classLoader = getClass().getClassLoader();
public Object afterBorn(Object obj, String beanName) {
Mirror<Object> mirror = Mirror.me(obj);
Field[] fields = Mirror.me(obj).getFields();
for (Field field : fields) {
RpcInject rpcInject = field.getAnnotation(RpcInject.class);
if (rpcInject == null)
continue;
AbstractRpcRefProxy proxy = Mirror.me(rpcInject.by()).born();
proxy.setField(field);
proxy.setIoc(ioc);
proxy.setRpcInject(rpcInject);
proxy.setObject(obj);
proxy.setLiteRpc(liteRpc);
Object t = Proxy.newProxyInstance(classLoader, new Class[] {field.getType()}, proxy);
mirror.setValue(obj, field.getName(), t);
proxy.afterInject();
rpcProxys.put(field.toGenericString(), proxy);
}
return obj;
}
public Object afterCreate(Object obj, String beanName) {
return obj;
}
public int getOrder() {
return 0;
}
public void depose() {
for (AbstractRpcRefProxy proxy : rpcProxys.values()) {
proxy.beforeDepose();
}
}
}

View File

@ -1,32 +0,0 @@
package org.nutz.boot.starter.literpc.impl;
import java.lang.reflect.Method;
import org.nutz.lang.reflect.FastClassFactory;
import org.nutz.lang.reflect.FastMethod;
public class RpcInvoker {
protected Object obj;
protected FastMethod fastMethod;
protected Method method;
public void setObj(Object obj) {
this.obj = obj;
}
public void setFastMethod(FastMethod fastMethod) {
this.fastMethod = fastMethod;
}
public void setMethod(Method method) {
this.method = method;
setFastMethod(FastClassFactory.get(method));
}
public Object invoke(Object...args) throws Throwable {
return fastMethod.invoke(obj, args);
}
}

View File

@ -1,41 +0,0 @@
package org.nutz.boot.starter.literpc.impl;
import java.lang.reflect.Method;
import org.nutz.boot.starter.literpc.LiteRpc;
import org.nutz.boot.starter.literpc.api.RpcService;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
@IocBean(create="init")
public class RpcServiceScaner {
@Inject
protected LiteRpc liteRpc;
@Inject("refer:$ioc")
protected Ioc ioc;
public void init() {
for (String beanName : ioc.getNamesByType(RpcService.class)) {
if (Strings.isBlank(beanName))
continue;
Object obj = ioc.get(null, beanName);
for (Class<?> klass : obj.getClass().getInterfaces()) {
if (klass == RpcService.class)
continue;
if (RpcService.class.isAssignableFrom(klass)) {
for (Method method : klass.getMethods()) {
RpcInvoker invoker = new RpcInvoker();
invoker.setObj(obj);
invoker.setMethod(method);
liteRpc.registerInovker(LiteRpc.getMethodSign(method), invoker);
}
}
}
}
liteRpc.updateLoachRegInfo();
}
}

View File

@ -1,67 +0,0 @@
package org.nutz.boot.starter.literpc.impl.endpoint;
import java.io.ByteArrayOutputStream;
import org.nutz.boot.starter.literpc.RpcException;
import org.nutz.boot.starter.literpc.api.RpcReq;
import org.nutz.boot.starter.literpc.api.RpcResp;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.http.Header;
import org.nutz.http.Request;
import org.nutz.http.Response;
import org.nutz.http.Sender;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@IocBean
public class HttpRpcEndpoint implements RpcEndpoint {
public static String METHOD_HEADER_NAME = "LiteRpc-Method";
public static String SC_HEADER_NAME = "LiteRpc-Serializer";
public static String ENDPOINT_URI = "/literpc/endpoint";
public RpcResp send(RpcReq rpcReq, NutMap server, RpcSerializer serializer) {
String vip = server.getString("vip");
int port = server.getInt("port");
String url = "http://" + vip + ":" + port + ENDPOINT_URI;
Request req = Request.post(url);
Header header = req.getHeader();
header.clear();
header.set("Content-Type", "LiteRpcBody");
header.set(METHOD_HEADER_NAME, rpcReq.methodSign);
header.set(SC_HEADER_NAME, "jdk"); // 先固定为jdk,以后再扩展吧
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
try {
serializer.write(rpcReq.args, tmp);
}
catch (Exception e1) {
return new RpcResp(e1);
}
req.setData(tmp.toByteArray());
Sender sender = Sender.create(req);
sender.setConnTimeout(rpcReq.connectTimeout);
sender.setTimeout(rpcReq.timeout);
Response resp;
try {
resp = sender.send();
if (!resp.isOK()) {
throw new RpcException("endpoint resp code=" + resp.getStatus());
}
}
catch (Exception e) {
return new RpcResp(e);
}
try {
return (RpcResp) serializer.read(resp.getStream());
}
catch (Exception e) {
return new RpcResp(e);
}
}
public String getName() {
return "http";
}
}

View File

@ -1,156 +0,0 @@
package org.nutz.boot.starter.literpc.impl.endpoint;
import java.io.IOException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.boot.starter.literpc.LiteRpc;
import org.nutz.boot.starter.literpc.api.RpcResp;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.boot.starter.literpc.impl.RpcInvoker;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
@IocBean(create="_init")
public class HttpServletRpcEndpoint implements WebFilterFace, Filter {
private static final Log log = Logs.get();
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
@Inject
protected LiteRpc liteRpc;
protected boolean debug;
public void _init() {
debug = conf.getBoolean("literpc.endpoint.http.debug", false);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
// 首先,检查是否有LiteRpc特有的header
String methodSign = req.getHeader(HttpRpcEndpoint.METHOD_HEADER_NAME);
if (Strings.isBlank(methodSign)) {
if (debug)
log.debug("miss http header " + HttpRpcEndpoint.METHOD_HEADER_NAME);
resp.sendError(400);
return;
}
String scName = req.getHeader(HttpRpcEndpoint.SC_HEADER_NAME);
if (Strings.isBlank(scName)) {
if (debug)
log.debug("miss http header " + HttpRpcEndpoint.SC_HEADER_NAME);
resp.sendError(400);
return;
}
RpcSerializer serializer = liteRpc.getSerializer(scName);
if (serializer == null) {
if (debug)
log.debug("not support serializer=" + scName);
resp.sendError(400);
return;
}
// 本服务器是否支持这个method
RpcInvoker invoker = liteRpc.getInvoker(methodSign);
if (invoker == null) {
resp.setHeader("LiteRpc-Msg", "No such Method at this service methodSign="+methodSign);
if (debug)
log.debug("no such method methodSign=" + methodSign);
resp.sendError(404);
return;
}
// 反序列化方法参数
Object[] args;
try {
args = (Object[]) serializer.read(req.getInputStream());
}
catch (Throwable e) {
resp.setHeader("LiteRpc-Msg", "Serializer Exception when reading");
if (debug)
log.debug("Serializer Exception when reading", e);
resp.sendError(404);
return;
}
// 执行之
RpcResp rpcResp = new RpcResp();
try {
rpcResp.returnValue = invoker.invoke(args);
}
catch (Throwable e) {
rpcResp.err = e;
}
// 将结果序列化
try {
serializer.write(rpcResp, resp.getOutputStream());
}
catch (Throwable e) {
if (debug)
log.debug("Serializer Exception when writing", e);
if (resp.isCommitted()) {
// nothing we can do
}
else {
resp.reset();
resp.setHeader("LiteRpc-Msg", "Serializer Exception when write");
resp.sendError(404);
return;
}
}
}
//-------------------------------------------------------------------------------
public String getName() {
return "literpc";
}
public String getPathSpec() {
return HttpRpcEndpoint.ENDPOINT_URI;
}
public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST);
}
public Filter getFilter() {
return this;
}
public Map<String, String> getInitParameters() {
return new HashMap<>();
}
public int getOrder() {
return WebFilterFace.FilterOrder.NutFilter - 5;
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}

View File

@ -1,13 +0,0 @@
package org.nutz.boot.starter.literpc.impl.endpoint;
import org.nutz.boot.starter.literpc.api.RpcReq;
import org.nutz.boot.starter.literpc.api.RpcResp;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.lang.util.NutMap;
public interface RpcEndpoint {
RpcResp send(RpcReq req, NutMap server, RpcSerializer serializer);
String getName();
}

View File

@ -1,52 +0,0 @@
package org.nutz.boot.starter.literpc.impl.proxy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import org.nutz.boot.starter.literpc.LiteRpc;
import org.nutz.boot.starter.literpc.annotation.RpcInject;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.boot.starter.literpc.impl.endpoint.RpcEndpoint;
import org.nutz.ioc.Ioc;
import org.nutz.lang.Strings;
public abstract class AbstractRpcRefProxy implements InvocationHandler {
protected Field field;
protected RpcInject rpcInect;
protected Ioc ioc;
protected Object object;
protected LiteRpc liteRpc;
protected RpcEndpoint endpoint;
protected RpcSerializer serializer;
public void setField(Field field) {
this.field = field;
}
public void setRpcInject(RpcInject rpcInect) {
this.rpcInect = rpcInect;
}
public void setIoc(Ioc ioc) {
this.ioc = ioc;
}
public void setObject(Object object) {
this.object = object;
}
public void setLiteRpc(LiteRpc liteRpc) {
this.liteRpc = liteRpc;
}
public void afterInject() {
endpoint = liteRpc.getEndpoint(Strings.sBlank(rpcInect.endpointType(), "http"));
serializer = liteRpc.getSerializer(Strings.sBlank(rpcInect.serializer(), "jdk"));
}
public void beforeDepose() {
}
}

View File

@ -1,39 +0,0 @@
package org.nutz.boot.starter.literpc.impl.proxy;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.nutz.boot.starter.literpc.LiteRpc;
import org.nutz.boot.starter.literpc.RpcException;
import org.nutz.boot.starter.literpc.api.RpcReq;
import org.nutz.boot.starter.literpc.api.RpcResp;
import org.nutz.lang.util.NutMap;
public class DefaultRpcInjectProxy extends AbstractRpcRefProxy {
protected AtomicLong AL = new AtomicLong();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 构建RpcReq
RpcReq req = new RpcReq();
req.args = args;
req.object = proxy;
req.connectTimeout = rpcInect.connectTimeout() == -1 ? 1000 : rpcInect.connectTimeout();
req.timeout = rpcInect.timeout() == -1 ? 1000 : rpcInect.timeout();
req.method = method;
req.methodSign = LiteRpc.getMethodSign(method);
// 获取支持该方法的服务器信息
List<NutMap> servers = liteRpc.getServers(req.methodSign);
if (servers == null || servers.isEmpty()) {
throw new RpcException("No server support -> [" + method + "]");
}
// 选一个,执行之
NutMap server = servers.get((int)(AL.incrementAndGet() % servers.size()));
RpcResp resp = endpoint.send(req, server, serializer);
if (resp.err == null)
return resp.returnValue;
throw resp.err;
}
}

View File

@ -1,34 +0,0 @@
package org.nutz.boot.starter.literpc.impl.serializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import org.nutz.boot.starter.literpc.api.RpcSerializer;
import org.nutz.ioc.loader.annotation.IocBean;
/**
* JDK原生序列化, 最基本的,同时也是兼容性最好的序列化器
* @author wendal
*
*/
@IocBean
public class JdkRpcSerializer implements RpcSerializer {
public void write(Object obj, OutputStream out) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(obj);
oos.flush();
}
public Object read(InputStream ins) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(ins);
return ois.readObject();
}
public String getName() {
return "jdk";
}
}

View File

@ -1 +0,0 @@
org.nutz.boot.starter.literpc.impl.RpcServiceScaner

View File

@ -1,11 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-cloud</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<artifactId>loach-client</artifactId>
<groupId>org.nutz.cloud</groupId>
</project>

View File

@ -1,277 +0,0 @@
package org.nutz.boot.starter.loach.client;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.nutz.boot.AppContext;
import org.nutz.boot.NbApp;
import org.nutz.http.Request;
import org.nutz.http.Request.METHOD;
import org.nutz.http.Response;
import org.nutz.http.Sender;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import org.nutz.lang.hardware.NetworkType;
import org.nutz.lang.hardware.Networks;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.runner.NutRunner;
/**
* '泥鳅'客户端
*
* @author wendal
*
*/
@IocBean(create = "init", depose = "depose")
public class LoachClient extends NutRunner {
public LoachClient() {
super("loach.client");
}
private static final Log log = Logs.get();
@Inject
protected PropertiesProxy conf;
@Inject
protected AppContext appContext;
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected NbApp nbApp;
protected String id;
protected int startUpDelay;
protected ExecutorService es;
protected NutRunner updater;
protected List<UpdateListener> listeners = new LinkedList<>();
public static interface UpdateListener {
void onUpdate(Map<String, List<NutMap>> services);
}
public void depose() {
getLock().stop();
if (updater != null)
updater.getLock().stop();
es.shutdown();
}
public void init() {
startUpDelay = conf.getInt("loach.client.startUpDelay", -1);
es = Executors.newCachedThreadPool();
id = R.UU32();
url = conf.get("loach.client.url", "http://127.0.0.1:8610/loach/v1");
for (String name : ioc.getNamesByType(UpdateListener.class)) {
addListener(ioc.get(UpdateListener.class, name));
}
setDebug(conf.getBoolean("loach.client.debug", false));
if (conf.getBoolean("loach.client.enable", true)) {
if (Strings.isBlank(conf.get("nutz.application.name"))) {
throw new RuntimeException("need nutz.application.name");
}
}
if (conf.getBoolean("loach.client.enable", true)) {
es.submit(this);
}
if (conf.getBoolean("loach.updater.enable", true)) {
updater = new NutRunner("loach.updater." + url) {
public long exec() throws Exception {
LoachClient.this.updateServiceList();
return conf.getInt("loach.client.updater.interval", 3000);
}
};
updater.setDebug(isDebug());
es.submit(updater);
}
}
public Map<String, List<NutMap>> getServiceList() {
return Collections.unmodifiableMap(serviceList);
}
public List<NutMap> getService(String name) {
if (serviceList == null)
return null;
return serviceList.get(name);
}
protected String getUrls() {
return conf.get("loach.client.url", "http://127.0.0.1:8610/loach/v1");
}
protected String getServiceName() {
return conf.get("nutz.application.name", "demo");
}
protected long getPingInterval() {
return conf.getLong("loach.client.ping.interval", 3000);
}
protected int pingRetryCount;
public String url;
protected String lastPingETag = "ABC";
protected String lastListETag = "ABC";
protected boolean regOk;
protected boolean _reg(String regData) {
try {
String regURL = url + "/reg";
if (isDebug()) {
log.debug("Reg URL :" + regURL);
log.debug("Reg Data:" + regData);
}
Request req = Request.create(regURL, METHOD.POST);
req.setData(regData);
req.getHeader().clear();
req.getHeader().asJsonContentType();
Response resp = Sender.create(req).setTimeout(3000).send();
if (resp.isOK()) {
NutMap re = Json.fromJson(NutMap.class, resp.getReader());
if (re != null && re.getBoolean("ok", false)) {
log.infof("Reg Done id=%s url=%s", id, url);
regOk = true;
return true;
}
}
}
catch (Throwable e) {
log.debugf("bad url? %s %s", url, e.getMessage());
}
return false;
}
protected boolean _ping() {
try {
String pingURL = url + "/ping/" + getServiceName() + "/" + id;
if (isDebug())
log.debug("Ping URL=" + pingURL);
Request req = Request.create(pingURL, METHOD.GET);
req.getHeader().clear();
req.getHeader().set("If-Not-Match", lastPingETag);
Response resp = Sender.create(req, conf.getInt("loach.client.ping.timeout", 1000)).setConnTimeout(1000).send();
String cnt = resp.getContent();
if (isDebug())
log.debug("Ping result : " + cnt);
if (resp.isOK()) {
lastPingETag = Strings.sBlank(resp.getHeader().get("ETag"), "ABC");
NutMap re = Json.fromJson(NutMap.class, cnt);
if (re != null && re.getBoolean("ok", false))
return true;
} else if (resp.getStatus() == 304) {
return true;
}
}
catch (Throwable e) {
log.debugf("bad url? %s %s", url, e.getMessage());
}
return false;
}
/**
* 主逻辑
*/
public long exec() throws Exception {
// 启动延时
if (startUpDelay > 0) {
log.debug("start up delay " + startUpDelay + "ms");
int delay = startUpDelay;
startUpDelay = 0;
return delay;
} else if (startUpDelay == -1) {
if (!nbApp.isStarted())
return 100;
}
// 心跳
if (regOk) {
// 尝试心跳,成功就是返回
if (_ping()) {
if (pingRetryCount > 0 && isDebug())
log.debug("loach client ping OK");
pingRetryCount = 0;
return getPingInterval();
} else {
if (pingRetryCount < 5) {
pingRetryCount++;
log.info("loach client ping FAIL count=" + pingRetryCount);
return getInterval();
} else {
regOk = false;
log.info("loach client ping FAIL too many time, redo reg!");
}
}
}
NutMap regInfo = new NutMap();
regInfo.put("id", id);
regInfo.put("name", getServiceName());
regInfo.put("vip", conf.get("server.vip", "127.0.0.1"));
regInfo.put("port", appContext.getServerPort(null));
regInfo.put("eth.mac", Networks.mac(NetworkType.LAN));
regInfo.put("eth.ipv4", Networks.ipv4(NetworkType.LAN));
regInfo.putAll(EXT_REG_DATA);
String regData = Json.toJson(regInfo, JsonFormat.compact());
_reg(regData);
return getInterval();
}
public Map<String, List<NutMap>> serviceList = new HashMap<>();
protected long lastChecked;
@SuppressWarnings("unchecked")
public void updateServiceList() {
try {
String listURL = url + "/list";
Request req = Request.create(listURL, METHOD.GET);
req.getHeader().clear();
req.getHeader().set("If-Not-Match", lastListETag);
Response resp = Sender.create(req).setConnTimeout(1000).setTimeout(3000).send();
if (resp.isOK()) {
serviceList = (Map<String, List<NutMap>>) Json.fromJson(NutMap.class, resp.getReader()).get("data");
for (UpdateListener listener : listeners) {
listener.onUpdate(serviceList);
}
lastChecked = System.currentTimeMillis();
lastListETag = Strings.sBlank(resp.getHeader().get("ETag", "ABC"));
} else if (resp.getStatus() == 304) {
// ok
lastChecked = System.currentTimeMillis();
}
}
catch (Throwable e) {
log.debugf("bad url? %s %s", url, e.getMessage());
}
}
public void addListener(UpdateListener listener) {
if (this.listeners.contains(listener))
return;
listeners.add(listener);
}
public static NutMap EXT_REG_DATA = new NutMap();
}

View File

@ -1,40 +0,0 @@
package org.nutz.boot.starter.loach.client;
import org.nutz.boot.annotation.PropDoc;
import org.nutz.boot.starter.ServerFace;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
public class LoachClientStarter implements ServerFace {
@PropDoc(value = "是否启用'泥鳅'客户端", defaultValue = "true")
public static final String PROP_CLIENT_ENABLE = "loach.client.enable";
@PropDoc(value = "是否启用'泥鳅'服务器列表更新", defaultValue = "true")
public static final String PROP_UPDATER_ENABLE = "loach.updater.enable";
@PropDoc(value = "对外服务的虚拟ip或者域名", defaultValue="127.0.0.1")
public static final String PROP_HOST = "server.vip";
@PropDoc(value = "心跳频率,单位毫秒", defaultValue = "3000", type = "int")
public static final String PROP_PING_INTERVAL = "loach.client.ping.interval";
@PropDoc(value = "启动延时,单位毫秒", defaultValue = "-1", type = "int")
public static final String PROP_STARTUP_DELAY = "loach.client.startUpDelay";
@PropDoc(value = "'泥鳅'服务器的URL,可以多个,用分号隔开即可", defaultValue = "http://127.0.0.1:8610/loach/v1")
public static final String PROP_URLS = "loach.client.urls";
@PropDoc(value = "调试模式", defaultValue = "false")
public static final String PROP_DEBUG = "loach.client.debug";
@Inject("refer:$ioc")
protected Ioc ioc;
public void start() throws Exception {
ioc.get(LoachClient.class);
}
}

View File

@ -1 +0,0 @@
org.nutz.boot.starter.loach.client.LoachClientStarter

View File

@ -1,33 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-cloud</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<groupId>org.nutz.cloud</groupId>
<artifactId>loach-server</artifactId>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-nutz-mvc</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,46 +0,0 @@
package org.nutz.cloud.loach.server;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Lang;
import org.nutz.mvc.View;
import org.nutz.mvc.ViewMaker;
/**
* 为了减少 客户端与服务器端之间的流量, 做点黑魔法. <p/>
* 如果客户端发送了ETag,与服务器端的计算结果一样,那就是没变更啦,304!
* @author wendal
*
*/
@IocBean(name="$views_json304")
public class Json304ViewMaker implements ViewMaker, View {
private static JsonFormat jf = JsonFormat.compact();
@Override
public View make(Ioc ioc, String type, String value) {
if ("json304".equals(type))
return this;
return null;
}
public void render(HttpServletRequest req, HttpServletResponse resp, Object obj) throws Throwable {
String data = obj instanceof String ? obj.toString() : Json.toJson(obj, jf);
String sha1 = Lang.sha1(data);
byte[] re = data.getBytes();
if (sha1.equalsIgnoreCase(req.getHeader("If-None-Match"))) {
resp.setStatus(304);
}
else {
resp.setContentLength(re.length);
resp.setHeader("ETag", sha1);
resp.getOutputStream().write(re);
}
}
}

View File

@ -1,12 +0,0 @@
package org.nutz.cloud.loach.server;
import org.nutz.boot.NbApp;
public class LoachServerLauncher {
// 访问地址是 http://127.0.0.1:8610
public static void main(String[] args) {
new NbApp().setArgs(args).setPrintProcDoc(true).run();
}
}

View File

@ -1,190 +0,0 @@
package org.nutz.cloud.loach.server.module;
import static org.nutz.integration.jedis.RedisInterceptor.jedis;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.lang.util.Regex;
import org.nutz.mvc.adaptor.JsonAdaptor;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.DELETE;
import org.nutz.mvc.annotation.GET;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST;
@IocBean
@At("/loach/v1")
@Ok("json304")
public class LoachV1Module {
protected JsonFormat jsonFormat = JsonFormat.compact();
@Inject
protected PropertiesProxy conf;
/**
* 供客户端心跳入口
*/
@At({"/ping", "/ping/?/?"})
@Aop("redis")
@GET
public String ping(String serviceName, String id) {
if (id != null && id.length() < 30) {
long re = jedis().expire("loach:service:" + serviceName + ":" + id, getPingTimeout() / 1000);
if (re == 0) {
return "{ok:false}";
}
}
return "{ok:true}";
}
/**
* 注册一个服务,必须带service.vip和service.name,其他属性可任选.
*/
@AdaptBy(type = JsonAdaptor.class)
@POST
@At
@Aop("redis")
public NutMap reg(NutMap params) {
NutMap map = new NutMap();
// 检查基本的信息
if (Strings.isBlank(params.getString("vip"))) {
map.put("err", "miss vip");
return map;
}
String serviceName = (String) params.remove("name");
if (Strings.isBlank(serviceName)) {
map.put("err", "miss name");
return map;
}
if (!Regex.match("^[a-zA-Z0-9_-]{3,16}$", serviceName)) {
map.put("err", "name is invaild");
return map;
}
String regJson = Json.toJson(params, jsonFormat);
if (regJson.length() > getRegMaxSize()) {
map.put("err", "reg info is too big");
return map;
}
String id = params.getString("id");
if (id == null)
id = R.UU32();
jedis().setex("loach:service:" + serviceName + ":" + id, getPingTimeout() / 1000, regJson);
map.setv("ok", true).setv("id", id);
return map;
}
@At
@DELETE
@Ok("void")
public void unreg(String serviceName, String id) {
if (isAllowUnreg())
jedis().del("loach:service:" + serviceName + ":" + id);
}
@At("/list/?")
@Aop("redis")
public NutMap list(String serviceName) {
List<String> keys = new ArrayList<>(jedis().keys("loach:service:" + serviceName + ":*"));
Collections.sort(keys);
NutMap re = new NutMap();
re.put("ok", true);
List<NutMap> services = new LinkedList<>();
for (String key : keys) {
String cnt = jedis().get(key);
if (cnt == null)
continue;
NutMap serviceInfo = Json.fromJson(NutMap.class, cnt);
services.add(serviceInfo);
}
re.put("data", new NutMap(serviceName, services));
return re;
}
@At("/list")
public NutMap listAll() {
NutMap re = new NutMap();
re.put("ok", true);
re.put("data", getAllServices());
return re;
}
@Ok("raw")
@At("/list/forlook")
public String listAllForLook(boolean verbose) {
StringBuilder sb = new StringBuilder();
Map<String, List<NutMap>> services = getAllServices();
String NL = "\r\n";
sb.append("Service Count : ").append(services.size()).append(NL);
if (services.size() > 0) {
sb.append("Services :").append(NL);
for (String serviceName : services.keySet()) {
sb.append(" - ").append(serviceName).append(" :").append(NL);
for (NutMap service : services.get(serviceName)) {
sb.append(" - ").append(service.get("id"));
if (verbose) {
sb.append(":").append(NL);
for (Map.Entry<String, Object> en2 : service.entrySet()) {
sb.append(" - ").append(en2.getKey()).append(" : ").append(en2.getValue()).append(NL);
}
} else {
sb.append(" : ").append(service.get("vip")).append(":").append(service.getInt("port")).append(NL);
}
}
}
}
return sb.toString();
}
@Aop("redis")
protected Map<String, List<NutMap>> getAllServices() {
List<String> keys = new ArrayList<>(jedis().keys("loach:service:*"));
Collections.sort(keys);
Map<String, List<NutMap>> services = new HashMap<>();
for (String key : keys) {
String[] tmp = key.split("\\:");
if (tmp.length != 4)
continue;
String cnt = jedis().get(key);
if (cnt == null)
continue;
NutMap serviceInfo = Json.fromJson(NutMap.class, cnt);
List<NutMap> infos = services.get(tmp[2]);
if (infos == null) {
infos = new LinkedList<>();
services.put(tmp[2], infos);
}
serviceInfo.put("id", tmp[3]);
infos.add(serviceInfo);
}
return services;
}
public int getPingTimeout() {
return conf.getInt("loach.server.ping.timeout", 15000);
}
public int getRegMaxSize() {
return conf.getInt("loach.server.reg.maxSize", 8192);
}
public boolean isAllowUnreg() {
return conf.getBoolean("loach.server.unreg.enable", false);
}
}

View File

@ -1,3 +0,0 @@
nutz.application.name=loach-server
server.port=8610
server.ip=0.0.0.0

View File

@ -1,12 +0,0 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.logger.org.apache.jasper=info
log4j.logger.org.nutz.ioc=warn
log4j.logger.org.nutz.mvc=info
log4j.logger.org.nutz.resource=info
log4j.logger.io.swagger=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>'泥鳅'服务器</title>
</head>
<body>
<div>
<h2>这里提供服务注册/服务发现</h2>
</div>
<div>
<h2>
<a href="loach/v1/list/forlook">查看已注册的服务</a>
</h2>
</div>
</body>
</html>

View File

@ -1,36 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-parent</artifactId>
<version>2.2-SNAPSHOT</version>
</parent>
<artifactId>nutzboot-cloud</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-core</artifactId>
</dependency>
</dependencies>
<modules>
<module>loach-client</module>
<module>literpc</module>
</modules>
<profiles>
<profile>
<id>demo</id>
<modules>
<module>cloud-demo</module>
</modules>
</profile>
<profile>
<id>standalone</id>
<modules>
<module>loach-server</module>
<module>gateway-server</module>
</modules>
</profile>
</profiles>
</project>