月度归档:2012年06月

Restlet使用之Guice扩展

如果你像我一样对SpringFramework的配置感到头疼,并且不需要SpringFramework的深度支持,只需要Ioc,选择Guice吧!

https://code.google.com/p/guice/
https://github.com/restlet/restlet-framework-java/tree/master/incubator/org.restlet.ext.guice

下面通过使用 Gucie 的AOP支持在Restlet实现一个简单的节流阀。

示例代码片段:
配置Logger
初始化Guice, 分离Module(这里将资源和服务分离,方便对服务层进行单元测试)
映射 Public & Protected 路由

@Override
    public Restlet createInboundRoot() {
        // org.restlet.engine.loggerFacadeClass=org.restlet.ext.slf4j.Slf4jLoggerFacade
        SLF4JBridgeHandler.install();
        System.setProperty("org.restlet.engine.loggerFacadeClass", "org.restlet.ext.slf4j.Slf4jLoggerFacade");
      
        Context ctx = getContext();

        logger.info("Initializing Gucie...");
        //
        final Module serviceModule = new ServiceModule();
        // Resource module
        final Module serverResourceModule = new ResourceModule();
        // We use explicit Injector creation.
        // Initialize Guice
        Injector injector = RestletGuice.createInjector(serviceModule, serverResourceModule);
        // Initialize Restlet Guice extension.
        FinderFactory di = injector.getInstance(FinderFactory.class);

        logger.info("Gucie Initialized.");
        logger.info("Mapping routes...");
        // public router
        Router router = new Router(ctx);
        PathMappingHelper.mapPublic(router, di);
        // protected router
        Router sercurityRouter = new Router(ctx);
        PathMappingHelper.mapSecurity(sercurityRouter, di);
        logger.info("Routes mapped.");

        logger.info("Attaching security guard...");

        // Security guard, verify token
        // eg:
        // HTTP header
        // Authorization: token ASD12312312ASDASD12312123123
        ChallengeAuthenticator guard = new ChallengeAuthenticator(getContext(), ChallengeScheme.CUSTOM, "token");
        guard.setVerifier(injector.getInstance(Verifier.class));
        guard.setNext(sercurityRouter);

        router.attach(guard);
        logger.info("Security guard attached");

        return router;
    }

Resource 模块映射,重点在于bindInterceptor

public class ResourceModule extends AbstractModule {

    @Override
    protected void configure() {
    
        bind(Verifier.class).to(TokenVerifier.class);

        bind(AccountResource.class).to(AccountResourceImpl.class);
   
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Throttling.class), new ThrottlingBlocker(
                getProvider(TokenStore.class)));

    }
}

Service 模块映射,本例没有用到,忽略之。

接下来看看如何实现节流阀(throttling)

首先定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Throttling {

}

实现MethodIntercepter
TokenStore是一个简单的缓存接口,可以使用一个Memcache的实现,用来存储counter信息,当然也可以使用数据库实现。
MethodInterceptor 是AOP联盟定义的标准接口。
这里使用Provider提供对象的延迟绑定。
这里我们假设所有接口都是以用户作为隔离 /account/{account}/,限制为 50次请求每API每秒每账户,2000次请求每API每小时每账户。

public class ThrottlingBlocker implements MethodInterceptor {

    private Provider<tokenstore> provider;

    public ThrottlingBlocker(Provider</tokenstore><tokenstore> provider) {
        this.provider = provider;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        ServerResource resource = (ServerResource) invocation.getThis();
        String key = (String) resource.getRequest().getAttributes().get("account");
        Object result = null;
        if (key != null) {
      
            // Step 1 - Find throttling info
            // Step 2 - Execute method
            // Step 3 - Increase counter
            // Step 4 - Write throttling info.

            // {accountId}_{classFullName}#{methodName}
            String throttlingKey = key + "_" + invocation.getClass().getName() + "#" + invocation.getMethod().getName();
            String totalThrottlingKey = throttlingKey + "_total";
            Object co = provider.get().get(throttlingKey);
            Object tco = provider.get().get(totalThrottlingKey);
            long count = 0L;
            long total = 0L;
            if (co != null) {
                count = Long.parseLong(String.valueOf(co));
            }
            if (tco != null) {
                total = Long.parseLong(String.valueOf(tco));
            }

            if (total > 2000) {
                throw new RuntimeException("Throttling total quote(2000/hour) exceeded!");
            }
            // 50 requests/second/account
            if (count > 50) {
                throw new RuntimeException("Throttling quote(50/second) exceeded!");
            }

            result = invocation.proceed();
            count++;
            total++;
            provider.get().set(throttlingKey, count, 1 * 1000);
            provider.get().set(totalThrottlingKey, total, 60 * 60 * 1000);

        } else {
            result = invocation.proceed();
        }
        return result;
    }

之后在你的Resource实现里简单加入注解到方法,这样就实现了一个非常简单的节流阀。

 @Throttling
 @Get("json")
 public List<message> represent() {

这里仅仅提供了实现节流阀的一种机制,当然我们也可通过其他的方式实现。比如使用 Java EE Servlet API 中的 Filter。其他拦截器,难点不是在于使用什么技术实现,而是节流策略本身,如何配置用户的请求限制,如何设置合理的参数,是否绑定安全系统等等,这一切就必须得经过实践来得出,软件开发从来不是仅仅在于技术,更在于如何针对某种需求使用最合适的解决方案,而这点,需要大量的经验和分析抽象的能力。

我们写程序不是为了提供让计算机可以理解的代码,是为了解决现实世界的问题,提高社会生产力,这是整个软件行业的价值所在!

Restlet Google Gson 扩展

做了2个版本的 Mobile Aware,一直使用Restlet作为服务框架提供Restful Web Service,之前一直都是使用Jackson作为Json转换类库,后来自从用了Google Gson,果断决定切换到Gson。以为,API更简洁,使用更方便,性能也不错,还有版本支持(这个还是很强大的,尤其对于API的版本升级意义重大)。无奈Restlet没有提供相关扩展,于是乎花了几个小时的时间写了一个Gson的扩展。

源码可以在下面的地址获得:

https://github.com/nealmi/restlet-framework-java/tree/master/modules/org.restlet.ext.gson

Restlet还是一个比较不错框架,使用简单,扩展容易,尽管有些小问题,对实际应用影响不大。

Jetty + Restlet + Guice + Gson 的组合我用起来十分顺手。最终部署的时候,打个bigjar,然后使用service wrapper配置成linux的系统服务。整个部署过程基本不会超过5分钟。