Eureka开放Api落地

前言

之前给原生Servlet应用集成过Eureka,现在想想加入了多个依赖让应用更加“胖”!便想着Eureka有没有开放的协议,我直接实现原生的Eureka协议实现Eureka的注册和心跳。我们先看看Eureka的Github研究一下源码。

Eureka开放协议:

Eureka REST operations · Netflix/eureka Wiki · GitHub

简单解释:其他非Java应用可以通过这些RestApi,进行Eureka服务支持。

实操

服务规划

服务名说明
EUREKA-SERVER原生EurekaServer
EUREKA-CLIENT原生EurekaClient
Eureka-Cleint-Api通过开放协议实现的Eureka协议

Server代码

/**
 * 启动注册中心
 */
@SpringBootApplication
@EnableEurekaServer
@ServletComponentScan
public class RegistrationCenterApplication {
    public static void main(String[] args) {
        SpringApplication.run(RegistrationCenterApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(){
        FilterRegistrationBean<Filter> rg = new FilterRegistrationBean<>();
        rg.setFilter(new BodyLogFilter());
        rg.addUrlPatterns("/eureka/*");
        rg.addServletRegistrationBeans();
        return rg;
    }
}
package org.example.listener;
import com.netflix.appinfo.InstanceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaServerStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 监听SpringEvent 获取EurekaServer 发生的事情
 */
@Component 
public class ClientChangeListener {
    private final static Logger log = LoggerFactory.getLogger(ClientChangeListener.class);

    @EventListener
    public void registerListen(EurekaInstanceRegisteredEvent event) {
        InstanceInfo instanceInfo = event.getInstanceInfo();
        StringBuilder sb = new StringBuilder();
        sb.append("服务名=").append(instanceInfo.getAppName()).append("服务的host名=").
                append(instanceInfo.getHostName()).append("服务的ip地址=").
                append(instanceInfo.getIPAddr()).append("服务的端口=").append(instanceInfo.getPort()).append("服务ID=").
                append(instanceInfo.getInstanceId());
        log.info(sb.toString());
    }

    @EventListener
    public void canceledListen(EurekaInstanceCanceledEvent event) {
        log.info("服务名={}下线了", event.getAppName());
        log.info("server地址信息{}", event.getServerId());

    }

    @EventListener
    public void renewedListen(EurekaInstanceRenewedEvent event) {
        log.info("服务名={}进行续约", event.getServerId() +"  "+ event.getAppName());
    }

    @EventListener
    public void listen(EurekaRegistryAvailableEvent event) {
        log.info("注册中心启动,{}", System.currentTimeMillis());
    }

    @EventListener
    public void listen(EurekaServerStartedEvent event) {
        log.info("注册中心服务端启动,{}", System.currentTimeMillis());
    }
}

//打印请求参数官方没有给Json示例自己写太麻烦等下发布直接Copy Eureka-Client的注册参数

package example.filter;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class RequestWrapper  extends HttpServletRequestWrapper {

    private final byte[] body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        try (InputStream inputStream = request.getInputStream()) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }

            body = byteArrayOutputStream.toByteArray();
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    public byte[] getBody() {
        return this.body;
    }


}
package example.filter;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponseWrapper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

public class ResponseWrapper extends HttpServletResponseWrapper {
    /**
     * This class implements an output stream in which the data is written into a byte array.
     * The buffer automatically grows as data is written to it. The data can be retrieved using toByteArray() and toString().
     Closing a ByteArrayOutputStream has no effect. The methods in this class can be called after the stream has been closed without generating an IOException.
     */
    private ByteArrayOutputStream buffer = null;//输出到byte array
    private ServletOutputStream out = null;
    private PrintWriter writer = null;

    public ResponseWrapper(HttpServletResponse resp) throws IOException {
        super(resp);
        buffer = new ByteArrayOutputStream();// 真正存储数据的流
        out = new WapperedOutputStream(buffer);
        writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding()));
    }

    /** 重载父类获取outputstream的方法 */
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }

    /** 重载父类获取writer的方法 */
    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        return writer;
    }

    /** 重载父类获取flushBuffer的方法 */
    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
        if (writer != null) {
            writer.flush();
        }
    }

    @Override
    public void reset() {
        buffer.reset();
    }

    /** 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 */
    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }

    /** 内部类,对ServletOutputStream进行包装 */
    private class WapperedOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream bos = null;

        public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException {
            bos = stream;
        }

        @Override
        public void write(int b) throws IOException {
            bos.write(b);
        }

        @Override
        public void write(byte[] b) throws IOException {
            bos.write(b, 0, b.length);
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener listener) {

        }
    }
}
package example.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@WebFilter(filterName = "bodyLogFilter" )
public class BodyLogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        String contentType = servletRequest.getContentType();
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String url = request.getRequestURI();
            String method = request.getMethod();
            if (contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                log.info("接口 {} 文件上传 不过滤", url);
                filterChain.doFilter(servletRequest, servletResponse);
            } else if (HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) ) {
                RequestWrapper requestWrapper = new RequestWrapper(request);
                String body = new String(requestWrapper.getBody(), servletRequest.getCharacterEncoding());
                log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, body);
                filterChain.doFilter(requestWrapper,
                        new ResponseWrapper((HttpServletResponse) servletResponse));
            } else if (HttpMethod.GET.matches(method)) {
                String queryString = request.getQueryString();
                log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, queryString);
                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                filterChain.doFilter(servletRequest, servletResponse);
            }
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }

    }

}

Eureka-Client-Api

核心API实现

package org.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.beans.factory.annotation.Value;

import java.util.LinkedHashMap;
import java.util.Map;

@RestController
@RequestMapping("eureka")
@Slf4j
public class EurekaClientApi {

   static Map<String,String> headers = new LinkedHashMap<>();
   static {
       headers.put("ACCEPT","application/json");
   }
    @Autowired
    private HttpClientUtil httpClientUtil;
    @Value("${eurekaServerAddress}")
    private String URL  ;

    /**
     * 获取某一个服务的全部实例
     * @param applictionName
     * @return
     */
    @GetMapping("getAppInfoByName")
    public String getAppInfoByName(@RequestParam("name") String applictionName){
        String url = URL +"/apps/"+applictionName ;
        //127.0.0.1:8081/eureka/getAppInfoByName?name=EUREKA-CLIENT

        return httpClientUtil.doGet(url,null ,headers) ;
    }


    /**
     * 注销xx应用
     * @param name
     * @param id
     * @return
     */
    @GetMapping("deleteAppByNameAndId")
    public String deleteAppByNameAndId(@RequestParam("name") String name ,@RequestParam("id")String id){
        String url = URL +"/apps/"+name+"/"+id ;
        //127.0.0.1:8081/eureka/deleteAppByNameAndId?name=EUREKA-CLIENT&id=192.168.2.106:EUREKA-CLIENT:8080
        return httpClientUtil.delete(url,headers) ;
    }

    /**
     *  发送心心跳
     * @param name
     * @param id
     * @return
     */
    @GetMapping("heartBeat")
    public String heartBeat(@RequestParam("name") String name ,@RequestParam("id")String id){
        String url = URL +"/apps/"+name+"/"+id ;
        //127.0.0.1:8081/eureka/heartBeat?name=EUREKA-CLIENT&id=192.168.2.106:EUREKA-CLIENT:8080
        return httpClientUtil.put(url,headers);
    }

    /**
     * 注册应用
     * @return
     */
    @PostMapping
    public String registerApp( String s){
        String url = URL +"/apps/"+"EUREKA-API-CLIENT";
        Map<String,String> info = new LinkedHashMap<>();
       /*
        {
            "instance": {
            "instanceId": "192.168.2.106:EUREKA-API-CLIENT:8081",
                    "app": "EUREKA-API-CLIENT",
                    "appGroupName": null,
                    "ipAddr": "192.168.2.106",
                    "sid": "na",
                    "homePageUrl": "http://192.168.2.106:8081/",
                    "statusPageUrl": "http://192.168.2.106:8081/actuator/info",
                    "healthCheckUrl": "http://192.168.2.106:8081/actuator/health",
                    "secureHealthCheckUrl": null,
                    "vipAddress": "EUREKA-API-CLIENT",
                    "secureVipAddress": "EUREKA-API-CLIENT",
                    "countryId": 1,
                    "dataCenterInfo": {
                "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
                        "name": "MyOwn"
            },
            "hostName": "192.168.2.106",
                    "status": "UP",
                    "overriddenStatus": "UNKNOWN",
                    "leaseInfo": {
                "renewalIntervalInSecs": 30,
                        "durationInSecs": 90,
                        "registrationTimestamp": 0,
                        "lastRenewalTimestamp": 0,
                        "evictionTimestamp": 0,
                        "serviceUpTimestamp": 0
            },
            "isCoordinatingDiscoveryServer": false,
                    "lastUpdatedTimestamp": 1723301723465,
                    "lastDirtyTimestamp": 1723301725975,
                    "actionType": null,
                    "asgName": null,
                    "port": {
                "$": 8081,
                        "@enabled": "true"
            },
            "securePort": {
                "$": 443,
                        "@enabled": "false"
            },
            "metadata": {
                "management.port": "8081"
            }
        }
        }

        */

        return httpClientUtil.doPost(url,info,headers);
    }
}

通过开放协议获取EUREKA-CLIENT全部实例

2024-08-11 11:54:56.329  INFO 3556 --- [nio-8761-exec-6] example.filter.BodyLogFilter             : 接口 /eureka/apps/EUREKA-CLIENT 请求方法 GET 过滤参数 null

通过开放协议注销EUREKA-CLIENT

2024-08-11 11:53:00.482  INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener  : 服务名=EUREKA-CLIENT下线了
2024-08-11 11:53:00.482  INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener  : server地址信息192.168.2.106:EUREKA-CLIENT:8080
2024-08-11 11:53:00.482  INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener  : 服务名=EUREKA-CLIENT下线了
2024-08-11 11:53:00.482  INFO 3556 --- [nio-8761-exec-8] o.example.listener.ClientChangeListener  : server地址信息192.168.2.106:EUREKA-CLIENT:8080
2024-08-11 11:53:00.482  INFO 3556 --- [nio-8761-exec-8] c.n.e.registry.AbstractInstanceRegistry  : Cancelled instance EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 (replication=false)

通过开放协议发EUREKA-CLIENT心跳

2024-08-11 11:57:38.788  INFO 3556 --- [nio-8761-exec-3] example.filter.BodyLogFilter             : 接口 /eureka/apps/EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 请求方法 PUT 过滤参数 
2024-08-11 11:57:38.788  INFO 3556 --- [nio-8761-exec-3] o.example.listener.ClientChangeListener  : 服务名=192.168.2.106:EUREKA-CLIENT:8080  EUREKA-CLIENT进行续约
2024-08-11 11:57:39.541  INFO 3556 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 10ms
2024-08-11 11:57:40.744  INFO 3556 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 2ms
2024-08-11 11:57:41.230  INFO 3556 --- [nio-8761-exec-2] example.filter.BodyLogFilter             : 接口 /eureka/apps/delta 请求方法 GET 过滤参数 null
2024-08-11 11:57:41.245  INFO 3556 --- [nio-8761-exec-4] example.filter.BodyLogFilter             : 接口 /eureka/apps/EUREKA-CLIENT/192.168.2.106:EUREKA-CLIENT:8080 请求方法 PUT 过滤参数 
2024-08-11 11:57:41.245  INFO 3556 --- [nio-8761-exec-4] o.example.listener.ClientChangeListener  : 服务名=192.168.2.106:EUREKA-CLIENT:8080  EUREKA-CLIENT进行续约

通过开放协议注册EUREKA-CLIENT-API

//参数太多了写的麻烦直接API访问吧

2024-08-11 12:20:00.670  INFO 15052 --- [nio-8761-exec-2] example.filter.BodyLogFilter             : 接口 /eureka/apps/EUREKA-API-CLIENT 请求方法 POST 过滤参数 {"instance":{
  "instanceId":"192.168.2.106:EUREKA-API-CLIENT:8081",
  "app":"EUREKA-API-CLIENT",
  "appGroupName":null,
  "ipAddr":"192.168.2.106",
  "sid":"na",
  "homePageUrl":"http://192.168.2.106:8081/",
  "statusPageUrl":"http://192.168.2.106:8081/actuator/info",
  "healthCheckUrl":"http://192.168.2.106:8081/actuator/health",
  "secureHealthCheckUrl":null,"vipAddress":"EUREKA-API-CLIENT",
  "secureVipAddress":"EUREKA-API-CLIENT","countryId":1,
  "dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo","name":"MyOwn"},
  "hostName":"192.168.2.106","status":"UP",
  "overriddenStatus":"UNKNOWN",
  "leaseInfo":{"renewalIntervalInSecs":30,
  "durationInSecs":90,"registrationTimestamp":0,
  "lastRenewalTimestamp":0,"evictionTimestamp":0,
  "serviceUpTimestamp":0},
  "isCoordinatingDiscoveryServer":false,
  "lastUpdatedTimestamp":1723301723465,
  "lastDirtyTimestamp":1723301725975,"actionType":null,
  "asgName":null,"port":{"$":8081,"@enabled":"true"},
  "securePort":{"$":443,"@enabled":"false"},
  "metadata":{"management.port":"8081"}}}

2024-08-11 12:20:00.671  INFO 15052 --- [nio-8761-exec-2] o.example.listener.ClientChangeListener  : 服务名=EUREKA-API-CLIENT服务的host名=192.168.2.106服务的ip地址=192.168.2.106服务的端口=8081服务ID=192.168.2.106:EUREKA-API-CLIENT:8081
2024-08-11 12:20:00.671  INFO 15052 --- [nio-8761-exec-2] c.n.e.registry.AbstractInstanceRegistry  : Registered instance EUREKA-API-CLIENT/192.168.2.106:EUREKA-API-CLIENT:8081 with status UP (replication=false)
2024-08-11 12:20:01.456  I

Other

.......TODO ......

总结

原态Servlet应用与其他语言应用即可以通过当前Rest Api 实现EurekaClient。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值