emscripten_fetchをRustで使う場合のrequestHeadersの設定

emscriptenで、元々はJavaSciptで書いていたネットワーク周りのコードを、
fetch APIの`emscripten_fetch`で置き換えた時のメモ。

## 結論
`requestHeaders`には、`{ key, value, key, value, ..., 0 }`という形の、最後の要素が0の、文字列のポインタの配列を設定する。keyやvalueは、null-terminateされたUTF-8の文字列で作る。
こんな感じ。

```rust
    let k = "Content-type\0".to_string();
    let v = "application/json\0".to_string();
    let hdrs = vec![
      k.as_ptr(),
      v.as_ptr(),
      std::ptr::null(),
    ];
    (*attr.as_mut_ptr()).requestHeaders = hdrs.as_ptr() as *const *const std::os::raw::c_char;
```

## 準備
まず、emscriptenへのFFI bindingを作る必要があった。emscripten-sysは結構古くて、そのままでは動かなかったので、bindgenで作った。これがメモ。https://qiita.com/kzee/items/1a465722a2b4a9c69796

## ３通りのemscripten_fetchの動かし方
https://emscripten.org/docs/api_reference/fetch.html
によると、`emscripten_fetch`には、大きく分けて３通りの動かし方がある。

### 1. Asynchronous Fetch
https://qiita.com/_likr/items/71817953b237a32a3015
を参考にしたところ、問題なく動いた。どうもありがとうございました！

### 2. Synchronous Fetch
次に、同期のfetchを試してみようと思い、上述のページを見てみると、

```
–proxy-to-worker: Synchronous Fetch operations are allowed for fetches that only do an XHR but do not interact with IndexedDB.
```
とあるので、`–proxy-to-worker`をリンカーのフラグに足して、サンプルコードを動かしてみると、エラーも出ないが、何も起こらないし、同期的に動いているようには見えない。

後に、下のページから`–proxy-to-worker`ではなくて、`--proxy-to-worker`が正しいことを知った。。
https://emscripten.org/docs/tools_reference/emcc.html#proxy-to-worker

これでビルドしてみると、エラーが出る。`--proxy-to-worker`は、`MODULARIZE=1`と同時には使えないらしい。webpackを使っているため、このフラグは外せないので、同期のfetchは候補から脱落。

### 3. Waitable Fetches
全く動かなかったのでパス。

## Asynchronous Fetchで、requestHeadersを使う
Asynchronous Fetch以外は使えないので、その前提で、`Content-type: application/json`をヘッダーに含める方法を調べる。

前述のQuiitaのページのサンプルではrequestHeaderを使っていなかった。公式ページにも解説はない。レポジトリにもテストコードは見つからないが、
https://github.com/emscripten-core/emscripten/blob/master/system/include/emscripten/fetch.h に、

```cpp
// Points to an array of strings to pass custom headers to the request. This array takes the form
// {"key1", "value1", "key2", "value2", "key3", "value3", ..., 0 }; Note especially that the array
// needs to be terminated with a null pointer.
const char * const *requestHeaders;
```

とあったので、それっぽく書いてみた。

```rust
    let k = CString::new("Content-type").unwrap().as_ptr();
    let v = CString::new("application/json").unwrap().as_ptr();
    let hdrs = vec![
      k,
      v,
      std::ptr::null(),
    ];
    (*attr.as_mut_ptr()).requestHeaders = hdrs.as_ptr() as *const *const std::os::raw::c_char;
```

すると、ゴミのような文字列しか作られない。

https://stackoverflow.com/questions/44671703/how-to-setup-custom-header-fields-for-emscripten-fetch-api-from-c によると、1.37.13では、

```cpp
fetch->__attributes.requestHeaders = 0;// TODO:strdup(fetch->__attributes.requestHeaders);
```
となっていて、requestHeadersはサポートされていなかったようなので、念の為コードを確認すると、現バージョンではサポートされている。

https://github.com/emscripten-core/emscripten/blob/master/system/lib/fetch/emscripten_fetch.cpp

```c++
  if (fetch_attr->requestHeaders) {
    size_t headersCount = 0;
    while (fetch_attr->requestHeaders[headersCount])
      ++headersCount;
    const char** headers = (const char**)malloc((headersCount + 1) * sizeof(const char*));
    if (!headers) {
      fetch_free(fetch);
      return 0;
    }
    memset((void*)headers, 0, (headersCount + 1) * sizeof(const char*));

    for (size_t i = 0; i < headersCount; ++i) {
      headers[i] = strdup(fetch_attr->requestHeaders[i]);
      if (!headers[i])

      {
        for (size_t j = 0; j < i; ++j) {
          free((void*)headers[j]);
        }
        free((void*)headers);
        fetch_free(fetch);
        return 0;
      }
    }
    headers[headersCount] = 0;
    fetch->__attributes.requestHeaders = headers;
  }
```

悩みながら、いろいろとページを見ていたところ、下のページで答えがみつかった。
https://qiita.com/dalance/items/a40bcf547215b69859fb

CStringをどこにもバインドしていなかったので、dropされてしまっていて、そのためにゴミが吐かれていた。。
どうもありがとうございました！

修正すると、`hdrs`に正しく文字列が入るようになった。

```rust
    let k = CString::new("Content-type").unwrap();
    let v = CString::new("application/json").unwrap();
    let hdrs = vec![
      k.as_ptr(),
      v.as_ptr(),
      std::ptr::null(),
    ];
    (*attr.as_mut_ptr()).requestHeaders = hdrs.as_ptr() as *const *const std::os::raw::c_char;
```

しかし、今度はJavaScriptレベルでエラーが出る。
どうやら、ヘッダーを設定するところで、UTF-8を期待しているようだったので、

```rust
    let k = "Content-type".to_string();
    let v = "application/json".to_string();
    let hdrs = vec![
      k.as_ptr(),
      v.as_ptr(),
      std::ptr::null(),
    ];
    (*attr.as_mut_ptr()).requestHeaders = hdrs.as_ptr() as *const *const std::os::raw::c_char;
```

してみると、エラーは出なくなったが、Content-typeの後におかしなキャラクタが入っていて、うまく動かない。そこで、文字列をnullターミネートした。

```rust
    let k = "Content-type\0".to_string();
    let v = "application/json\0".to_string();
    let hdrs = vec![
      k.as_ptr(),
      v.as_ptr(),
      std::ptr::null(),
    ];
    (*attr.as_mut_ptr()).requestHeaders = hdrs.as_ptr() as *const *const std::os::raw::c_char;
```

そしたら動きました！