faywong 发布的文章

由Mozilla开源的崩溃统计项目Socorro非常适合针对客户端的崩溃闪退、日志/堆栈上报,服务端进行采集、处理、分析、报告。

客户端的工作由类库[Breakpad](https://wiki.mozilla.org/Breakpad)完成。

服务端的工作由[Socorro](https://github.com/mozilla/socorro/)完成。

代码托管在[github](https://github.com/mozilla/socorro).

看这个提交和发布的数目,迭代程度还是挺深的。

这样针对移动开发,就不必重复造轮子了。

[Duktape](http://duktape.org/) 是一个体积小巧、可移植性高、适合嵌入到各种环境中的 JavaScript 引擎。

最近需要将 [protobuf.js](https://github.com/dcodeIO/protobuf.js) 移植到 Duktape 里边运行起来,所以需要解决 JavaScript 模块化加载问题,也就是要支持 require, module.exports 语法。我们通过 modSearch 函数来实现模块化加载:

#### 实现 modSearch 函数

[Implementing a native modSearch() function](http://wiki.duktape.org/HowtoModules.html)这篇 guide 里边有说通过在 native 实现 modSearch 函数就可以在 JavaScript 里通过require的时候加载到别的模块。

我在 c 层实现 modSearch 函数如下:

```c
//
// main.c
// duktape
//
// Created by faywong on 16/3/18.
// Copyright © 2016年 faywong. All rights reserved.
//

#include
#include "duktape.h"
#include "fileio.h"

duk_ret_t my_mod_search(duk_context *ctx) {
/*
* index 0: id (string)
* index 1: require (object)
* index 2: exports (object)
* index 3: module (object)
*/
printf("fun: %s in, id: %s\n", __FUNCTION__, duk_require_string(ctx, 0));

const char *id = duk_require_string(ctx, 0);
duk_pop_n(ctx, duk_get_top(ctx));

const int FILE_PATH_LEN = 1024;
char file[FILE_PATH_LEN];
memset(file, 0, FILE_PATH_LEN);
snprintf(file, FILE_PATH_LEN, "/Users/faywong/%s.js", id);

duk_push_string_file(ctx, file);
return 1;
}

/*
* Register Duktape.modSearch
*/
void register_mod_search(duk_context *ctx) {
duk_eval_string(ctx, "(function (fun) { Duktape.modSearch = fun; })");
duk_push_c_function(ctx, my_mod_search, 4 /*nargs*/);
duk_call(ctx, 1);
duk_pop(ctx);
}

int main(int argc, const char * argv[]) {

duk_context *ctx = duk_create_heap_default();

if (ctx) {

register_mod_search(ctx);

register_fileio(ctx);

duk_eval_file(ctx, "/Users/faywong/test.js");
printf("result is: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
}

return 0;
}
```

test.js 用以验证实现的模块化加载功能是否正常,内容如下:
```JavaScript
var ByteBuffer = require('bytebuffer');
var test = new ByteBuffer(10);
print('step 1, ByteBuffer ok: ' + test.toString());
var ProtoBuf = require('protobuf');
print('step 2, ProtoBuf ok: ' + (typeof ProtoBuf));
print('step 3, typeof ProtoBuf.loadProtoFile: ' + (typeof ProtoBuf.loadProtoFile));
var builder = ProtoBuf.loadProtoFile('/Users/faywong/complex.proto');
print('step 4, typeof builder: ' + (typeof builder));

Game = builder.build("Game"),
Car = Game.Cars.Car;

// OR: Construct with values from an object, implicit message creation (address) and enum values as strings:
var car = new Car({
"model": "Rustywq",
"vendor": {
"name": "Iron Inc.",
"address": {
"country": "US"
}
},
"speed": "SUPERFAST" // also equivalent to "speed": 2
});

// OR: It's also possible to mix all of this!

// Afterwards, just encode your message:
var buffer = car.encode();

print('step 5, typeof buffer: ' + (typeof buffer) + ' toString(): ' + buffer.toString());
```

其中:
* **register\_mod\_search** 函数用于向 Duktape 注册一个用于加载 JavaScript 模块的函数 **my\_mod\_search**,该函数有四个入参,分别为模块 id、发起 require 的模块、本模块的 exports 对象、本模块的 module 对象,该函数加载 /Users/faywong 目录下以 id 为主文件名(比如在 test.js 中 require 到的 bytebuffer, protobuf)的 JavaScript 文件并将文件内容返回给 Duktape

* 为了方便,test.js 中 require 的其他 JavaScript 模块被笔者放在了自己的家目录下:
```bash
/Users/faywong/bytebuffer.js
/Users/faywong/protobuf.js
/Users/faywong/test.js
```

Luminus web项目默认会启用anti-forgery特性以防止跨站攻击,极大增强了安全性。但是也为一些异步发生(不是来源于服务端的输出页面)的请求(ajax)带来了不便。

可以如下解决之:

若你的网页模板是经由selmer layout的,它会自动带上这个anti-forgery token。你便可以在js中添加如下代码

```javascript

var csrfToken = "{{csrf-token}}";
$.ajaxPrefilter(function (options, originalOptions, request) {
if (csrfToken) {
request.setRequestHeader('X-CSRF-Token', csrfToken);
}
});
```

Luminus框架的anti-forgery middleware也可以接受http header中的"X-CSRF-Token"指定的token。
这样子就把token给带到服务端了。让ajax请求顺利发出。

用了clojure,才发现北方有一座高山……

今天网站中需要做一个小功能,通过restful api请求另外一个内部网站的数据,但是写这个网站的同学给出的数据是[[a b c] [d e f]...]另外还有一个用于代表每一项的链接的links: [link1 link2 link3...]

需要将之每项对应合并起来,找了下发现map-indexed非常适合这个场景:

```clojure
(def test1 [['a ] ['b] ['c]])
(def test2 ['c 'd 'e])
(defn my-merge [vect1 vect2]
(into [] (map-indexed
(fn [idx item] (merge item (vect2 idx))) vect1)))

(my-merge test1 test2)

=>[[a c] [b d] [c e]]
```

在一些复杂的JNI调用中,比如JNI调用Java层的对象、Java层又调native方法,嵌套过多了,某一次调用产生的异常会在下一次调用JNI时被check出来,
这时候会产生如下日志:

```bash
01-13 21:22:43.247 24613-24613/com.somepkg A/art: art/runtime/check_jni.cc:65] JNI DETECTED ERROR IN APPLICATION: JNI NewByteArray called with pending exception 'java.lang.NullPointerException' thrown in unknown throw location
01-13 21:22:43.247 24613-24613/com.somepkg A/art: art/runtime/check_jni.cc:65] in call to NewByteArray
```

这种问题都是在Java代码中产生了异常,但是并不是所有case下都能一眼通过逻辑判断是哪行,这时候有个JNIEnv的方法能帮上我们大忙:
```c
jthrowable thr = (*env)->ExceptionOccurred(env);

if (thr) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
```

c++版本:
```c++
void CheckException(JNIEnv* env) {
if (!HasException(env)) return;

// Exception has been found, might as well tell breakpad about it.
jthrowable java_throwable = env->ExceptionOccurred();
if (!java_throwable) {
// Do nothing but return false.
CHECK(false);
}

// Clear the pending exception, since a local reference is now held.
env->ExceptionDescribe();
env->ExceptionClear();

// Set the exception_string in BuildInfo so that breakpad can read it.
// RVO should avoid any extra copies of the exception string.
base::android::BuildInfo::GetInstance()->set_java_exception_info(
GetJavaExceptionInfo(env, java_throwable));

// Now, feel good about it and die.
CHECK(false);
}
```

在被checkjni侦测到异常的代码(比如上例中是NewByteArray)之前加上如上代码就可以将Java层的异常信息给优雅地打印出来,从而精准定位问题。

另一种定位此类问题的方式是打开Android设备的CheckJni功能。但是一般production设备上都不太容易实现。所以强力推荐以上方法。