jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

elasticsearch源码分析之plugin的开发

作者:jasper | 分类:ElasticSearch | 标签:   | 阅读 3080 次 | 发布:2016-06-19 9:47 p.m.

在Elasticsearch中,如果你能自定义的扩充Elasticsearch的功能,比如说你想扩展自己的api、分词器、界面、权限等等,这时候你需要开发Elasticsearch的plugin了,之前也提到了Elasticsearch利用了Guice来达到注入各个功能模块的目的,这就让自定义的plugin也可以很方便地注入到Elasticsearch中,在网上看到过几篇这这方面的介绍,但都是两三年前的版本了,最新的Elasticsearch怎么来做到自开发plugin呢,本文就来简单地介绍下。

对于Elasticsearch的plugin可以分为两类:jvm和site;其中site就是说要实现前端界面的,其他的都可以归纳到jvm这一类中;和Elasticsearch中的模块一样,plugin也要实现Services和Module这两个部分。代码的结构大体长这个样子:

下面我们来分别看看这三个部分:

MyPlugin

继承自抽象类Plugin,其中nodeModules方法返回当前plugin的module模块,这个方法是必须的:

import org.elasticsearch.common.inject.Module;
import org.elasticsearch.plugins.Plugin;
import java.util.Collection;
import java.util.Collections;

public class MyPlugin extends Plugin{

@Override
public String name(){
    return "my-plugin";
}

@Override
public String description() {
    return "my plugin for demo";
}

@Override
public Collection<Module> nodeModules() {
   return Collections.<Module>singletonList(new MyModule());
}
}

MyModule

MyModule继承自AbstractModule,下面的实现方式是不是很熟悉啊,对滴,就是在org.elasticsearch.common.inject中的那一套,以此来达到“注入”的目的;其中真正的业务逻辑代码是在MyService中。

import org.elasticsearch.common.inject.AbstractModule;

public class MyModule extends AbstractModule
{

    @Override
    protected void configure()
    {
        bind(MyService.class).asEagerSingleton();
    }

}

MyService

MyService中实现真正的业务逻辑,我这里的demo含有对api的扩展,所以继承自了BaseRestHandler,其实这里就是具体情况具体的写法都不太一样了,需要读者自己去把握。

import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.*;
import org.elasticsearch.rest.RestRequest.Method;


import java.util.Arrays;
import java.util.List;


public class MyService extends BaseRestHandler {
    @Inject
    protected MyService(final Settings settings, RestController controller, Client client) {
        super(settings, controller, client);
        controller.registerHandler(Method.GET, "/hello/", this);
        controller.registerHandler(Method.GET, "/hello/{name}", this);
        controller.registerFilter(new RestFilter() {
            @Override
            public void process(RestRequest restRequest, RestChannel restChannel, RestFilterChain restFilterChain) throws Exception {

                if (restRequest.method() == Method.DELETE) {
                    restChannel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "forbidden delete method"));
                } else if(restRequest.uri().contains("_search")) {
                    List<String> indices = getIndices(restRequest);
                    String deny_indices = settings.get("deny_indices");
                    assert indices != null;
                    for(String index:indices){
                        if(index.contains(deny_indices)){
                            restChannel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "forbidden to operate index:"+index));
                        }
                    }
                    restFilterChain.continueProcessing(restRequest, restChannel);
                }else{
                restFilterChain.continueProcessing(restRequest, restChannel);
                }
            }
        });
    }

    @Override
    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
        logger.debug("HelloWorldAction.handleRequest called");
        final String name = request.hasParam("name") ? request.param("name") : "world";

        String content = "{\"success\":true, \"message\":\"hello " + name + "\"}";

        RestResponse response = new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, content);
        channel.sendResponse(response);
    }


    public static List<String> getIndices(final RestRequest request) {
        String[] indices = new String[0];
        final String path = request.path();
        System.out.println("Evaluate decoded path for indices'" + path + "'");

        if (!path.startsWith("/")) {
            return null;
        }

        if (path.length() > 1) {
            int endIndex;
            if ((path.indexOf('/', 1)) != -1) {
                endIndex = path.indexOf('/', 1);
            }else{
                endIndex = path.length();
            }

            if (!path.trim().startsWith("/_")) {
                indices = Strings.splitStringByCommaToArray(path.substring(1,endIndex));
            }
        }

        System.out.println("Indices: " + Arrays.toString(indices));
        return Arrays.asList(indices);

    }
}

上面的代码我就不一一去讲解了,代码比较简单,基本都能看懂吧,大体说一下提供了哪些功能吧,主要实现了三个功能:

  1. 扩展了一个hello接口;
  2. 禁止delete操作;
  3. 根据配置,禁止对配置的index做search操作;

OK,代码就这么些,接下来该如何把该plugin放到Elasticsearch中去呢:

1、将插件打成jar包后安装,可以用plugin命令安装,也可以手动去安装,在我们这里由于是IDE中运行,所以选择本地安装,在ES_HOME/plugins目录下创建一个名为MyPlugin的目录,该目录名称必须与插件名称相同(区分大小写),然后将jar包拷贝至MyPlugin目录中;

2、在该目录中创建plugin-descriptor.properties文件,在文件中填入下面几项:

description=my plugin for demo
version=1.0.0
jvm=true
#site=true
name=myplugin
elasticsearch.version=2.1.0
java.version=1.8.0
classname=org.elasticsearch.plugin.MyPlugin

其中jvm和site至少要有一项,当然可以同时充当jvm和site的角色,其他项也都是必须的,特别是classname是为了告诉Elasticsearch插件的实现类是哪一个。

3、重启Elasticsearch,就能看到plugin被注入进去啦:

具体的效果我就不贴图了,反正上面的功能都是能跑通的。

再来简单地提一下site类型的plugin吧,对于这种类型,上面的配置site=true需要配置进去,然后在上面的MyPlugin目录下面创建目录_site,在_site目录中可以写自己的html文件,默认是将index.html作为入口,然后就可以扩展出自己想要的界面了。

总结

内容大概就这么些吧,这里只是大体说了下应该怎么去自定义plugin,如果要实现很复杂的功能,可能需要你对Elasticsearch的源码有熟悉的了解。

这里还有个小小的问题,就是在开发plugin的时候,我暂时还没有发现有什么方法可以便于debug,如果按照上面的套路的话,每次都得打包、替换、重启,这是一个很不方便的过程,固然可以通过testCase来做debug,但是所见即所得的方式更能提高开发效率吧,读者如果有好的开发方式,烦请告知,万分感谢!!!


转载请注明出处:http://www.opscoder.info/es_plugin.html

其他分类: