2019/06/30


自宅で動かしている物体認識サーバは TensorFlow を使って Go で書かれていたのだけど、CPU 負荷が高いので以前 go-tflite で書き換えた。その後 Raspberry Pi Zero W でそのまま使えるだろうと思っていたら結構リソースが厳しかったので C++ なウェブサーバの実装である crow と TensorFlow Lite を使って書き換える事にした。

GitHub - ipkn/crow: Crow is very fast and easy to use C++ micro web framework (inspired by Python Flask)

How to Build If you just want to use crow, copy amalgamate/crow_all.h and include it. Requirements C...

https://github.com/ipkn/crow/

ただし問題があって、crow は最新の boost を使ってビルドするとエラーが発生する。issue に書いたら親切な人がパッチを貼ってくれたのでローカルの amalgamate コードに適用した。

Cannot build on boost 1.70 · Issue #340 · ipkn/crow · GitHub

g++ -std=c++14 -c -I../include -I. helloworld.cpp -o helloworld.o In file included from ../include/c...

https://github.com/ipkn/crow/issues/340

これで実装できるだろうと思っていたら、なんと crow はマルチパートアップロードをサポートしていない事に気付く。他のウェブサーバ実装を探しても良かったのだけど、どうせ家で動かすウェブサーバだしアタックを受ける事もないので若干雑な実装ながらマルチパートアップロードを実装した。

typedef struct {
  std::map<std::string, std::string> header;
  std::string body;
} part;

static void
trim_string(std::string& s, const char* cutsel = \t\v\r\n") {
  auto left = s.find_first_not_of(cutsel);
  if (left != std::string::npos) {
    auto right = s.find_last_not_of(cutsel);
    s = s.substr(left, right - left + 1);
  }
}

static std::vector<std::string>
split_string(std::string& s, const std::string& sep, int n = -1) {
  std::vector<std::string> result;
  auto pos = std::string::size_type(0);
  while (n != 0) {
    auto next = s.find(sep, pos);
    if (next == std::string::npos) {
      result.push_back(s.substr(pos));
      break;
    }
    result.push_back(s.substr(pos, next - pos));
    pos = next + sep.length();
    --n;
  }
  return result;
}

static std::map<std::string, std::string>
parse_header(std::string& lines) {
  std::map<std::string, std::string> result;
  std::string::size_type pos;
  for (auto& line : split_string(lines, "\r\n")) {
    auto token = split_string(line, ":"2);
    if (token.size() != 2)
      break;
    trim_string(token[0]);
    std::transform(token[0].begin(), token[0].end(), token[0].begin(), ::tolower);
    trim_string(token[1]);
    result[token[0]] = token[1];
  }
  return result;
}

static std::vector<part>
parse_multipart(const crow::request& req, crow::response& res) {
  auto ct = req.get_header_value("content-type");
  auto pos = ct.find("boundary=");

  std::vector<part> result;
  if (pos != std::string::npos) {
    auto boundary = "\n--" + ct.substr(pos + 9);
    pos = boundary.find(";");
    if (pos != std::string::npos)
      boundary = boundary.substr(0, pos);
    pos = 0;
    auto& body = req.body;
    while (true) {
      auto next = body.find(boundary, pos);
      if (next == std::string::npos)
        break;
      auto data = body.substr(pos + boundary.size() + 2, next);
      auto eos = data.find("\r\n\r\n");
      if (eos == std::string::npos)
        break;
      auto lines = data.substr(0, eos);
      part p = {
        .header = parse_header(lines),
        .body = data = data.substr(eos + 4)
      };
      result.push_back(p);
      pos = next + boundary.size() + 1;
      if (body.at(pos) == '-' && body.at(pos + 1) == '-'
          && body.at(pos + 2) == '\n')
        break;
      else if (body.at(pos) != '\n')
        break;
    }
  }
  return result;
}

API 部分しか実装していないので使う場合は以下の様に curl コマンドを使う。

$ curl -X POST http://localhost:8888/upload -F "file=@grace_hopper.png;type=image/png"
[{"label":"bow tie","index":458,"probability":0.996078}]

OpenCV を使ってるので OpenCV がサポートしている画像フォーマットであれば使えるはず。適当なレベルではあるけど、こういうの欲しいと思ってる人もいるんじゃないかなと思ったので GitHub に置いておいた。

GitHub - mattn/crow-tflite-object-detect: Object detection API server using crow webserver and TensorFlow Lite.

crow-tflite-object-detect Object detection API server using crow webserver. Usage -1 curl -X POST htt...

https://github.com/mattn/crow-tflite-object-detect
Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術 Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術
渋川 よしき
オライリージャパン 単行本(ソフトカバー) / ¥3,199 (2017年06月14日)
 
発送可能時間:

Posted at by



2019/06/18


2016年、普段から現場でGoを使っている名立たるGoプログラマの皆さんと一緒に「みんなのGo言語」という書籍を執筆させて頂きました。

「みんなのGo言語」は他のリファレンス本とは異なり、Go言語の最新事情をお伝えする事に主眼を置いて書きました。

インストール方法や使い方、モダンなテストの書き方、ツールの使い方等も執筆時点での最新情報を書かせて頂きました。

これはとても意義がある事だった感じています。

しかしこれは逆に、時間が経つにつれ執筆した内容が次第に古くなってしまうというリスクを伴います。幾つかの内容は、3年経った現在に合わなくなっている物も出てきました。紹介したツールの中には開発が止まってしまっている物もあれば、執筆時点で制限事項と記したけれども現在では解消している物も出てきています。そればかりではなく新しく追加されたコマンドや機能、新しい制限事項もあります。特に Go Module の扱いは、今後Go言語を扱う開発者にとって知っておくべき最重要項目とも言えるでしょう。


今回、「みんなのGo言語」の改訂というチャンスを頂き、内容を再び更新させて頂く事になりました。古い情報をバッサリと書き直している章もあります。Go言語で開発をする上での最新のヒントも書かせて頂きました。特に今回、新しい章「第7章 データベースの扱い方」を執筆させて頂きました。データベースを使ったウェブアプリケーションの作り方を分かりやすく書かせて頂いたつもりです。

ぜひ新しくなった「みんなのGo言語」をお楽しみ下さい。

改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 単行本(ソフトカバー) / ¥2,398 (2019年08月01日)
 
発送可能時間:

Kindle 版はこちら

改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 Kindle版 / ¥2,350 (2019年08月01日)
 
発送可能時間:

Posted at by



2019/05/16


Go は最近のプログラミング言語にしては珍しくポインタを扱えるプログラミング言語。とはいってもC言語よりも簡単で、オブジェクトの初期化やメソッドの定義以外の場所ではおおよそポインタを使っている様には見えない。メソッドやフィールドへのアクセスも . で出来るし Duck Type によりインタフェースを満たしていれば実体であろうとポインタであろうとそれほど意識する必要はない。ところがこの便利さに乗っかってしまうと思わぬ所で足をすくわれてしまう。

package main

type foo struct {
    v int
}

func (f foo) add(v int) {
    f.v = v
}

func main() {
    var a foo

    a.add(3)

    println(a.v)
}

このコードは 0 が表示される。メソッドを呼び出す際にはレシーバのオブジェクトを得る必要があるが、foo は実体なのでコピーが生成される。例えばレシーバ f が引数であったと考えると理解しやすくなる。

package main

type foo struct {
    v int
}

func add(f foo, v int) {
    f.v += v
}

func main() {
    var a foo

    add(a, 3)

    println(a.v)
}

メソッドとレシーバの関係は、実は「単なる関数と第一引数」と考えると分かりやすい。特にC言語でオブジェクト指向をやる様な人達は訓練されているので、Go をやる上でもこの辺りの動作を意識せず扱えているのかもしれない。

実体のレシーバにメソッドを生やすメリットが無い訳ではない。例えばオブジェクトの値を明示的に変更させたくない場合がそれで、そういった設計にしたい場合は戻り値を使う。

package main

type foo struct {
    v int
}

func (f foo) add(v int) foo {
    f.v += v
    return f
}

func main() {
    var a foo

    a = a.add(3)

    println(a.v)
}

これとは別に、ポインタと実体をまぜて考えてしまうと失敗してしまう事もある。

この例のポイントは「for range の反復変数はループ毎に新しいコピーが作成されない」という事。つまり上書きになる。プログラマは一見このループが実行されると以下の様になると考えてしまう。

= append(b, c[0].f())
= append(b, c[1].f())

だが実際はこう。

var i a
= c[0]
= append(b, i.f())
= c[1]
= append(b, i.f())

The Go Playground

slice の b は一見、c の情報が詰め込まれている様に見えるが、実際は i の情報が詰め込まれている。なので2回目の i への代入時に「i が持っていたレシーバの情報 c[0]c[1] 上書きされてしてしまう事になり、結果 a, b ではなく b, b が表示される。Method values という仕組みは参考として見ておくと良い。i への代入時点でレシーバの情報が デリファレンス されコピーされた状態で代入されているのがポイント、f() の呼び出し時の話ではない。

package main

import (
    "fmt"
)

type a struct {
    N string
}

func (s *a) f() func() {
    return func() {
        fmt.Printf("%s\n", s.N)
    }
}

func main() {
    b := []func(){}
    c := []a{{"a"}, {"b"}}

    var i a

    i = c[0]
    b = append(b, i.f())

    b[0]()
    i = c[1]
    b[0]() // この時点で既に b になる
    b = append(b, i.f())
}

まとめ

混ぜるな危険

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋
技術評論社 大型本 / ¥112 (2016年09月09日)
 
発送可能時間:

Posted at by