2018/05/13


AF_UNIX comes to Windows – Windows Command Line Tools For Developers

Introduction:   Beginning in Insider Build 17063 , you’ll be able to use the unix socket ( AF_UNIX )...

https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-comes-to-windows/

昨日、Windows 10 April 2018 Update が来た。WSL (Windows Subsystem for Linux) の常駐もちゃんと動く様になってた。仕組みはどうやら WSL 上のプロセスが一つでも生きていればバックグラウンドで Ubuntu.exe が生き続けてくれるという物らしい。WSL でも tmux が問題なく使える様になって開発しやすくなった。

ところでこの Windows 10 April 2018 Update には、開発者が待ちに待った Windows での AF_UNIX 対応が入っている。つまりは UNIX Domain Socket が Windows で動く様になったという事だ。物は試しと簡単なプログラムを作ってみた。 #ifdef _WIN32
# include <ws2tcpip.h>
# include <io.h>
#else
# include <sys/fcntl.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netdb.h>
# define closesocket(fd) close(fd)
#endif
#include <stdio.h>

#define UNIX_PATH_MAX 108

typedef struct sockaddr_un {
  ADDRESS_FAMILY sun_family;
  char sun_path[UNIX_PATH_MAX];
} SOCKADDR_UN, *PSOCKADDR_UN;

int
main(int argc, char* argv[]) {
  int server_fd;
  int client_fd;
  struct sockaddr_un server_addr; 
  size_t addr_len;

#ifdef _WIN32
  WSADATA wsa;
  WSAStartup(MAKEWORD(22), &wsa);
#endif

  unlink("./server.sock");

  if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    perror("server: socket");
    exit(1);
  }

  memset((char *) &server_addr, 0sizeof(server_addr));
  server_addr.sun_family = AF_UNIX;
  strcpy(server_addr.sun_path, "./server.sock");

  if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("server: bind");
    exit(1);
  }

  if (listen(server_fd, 5) < 0) {
    perror("server: listen");
    closesocket(server_fd);
    exit(1);
  }

  while (1) {
    if ((client_fd = accept(server_fd, NULLNULL)) < 0) {
      perror("server: accept");
      break;
    }
    while (1) {
      char buf[256];
      int n = recv(client_fd, buf, sizeof(buf), 0);
      if (n <= 0break;
      buf[n] = 0;
      puts(buf);
    }
    closesocket(client_fd);
  }

  closesocket(server_fd);

#ifdef _WIN32
  WSACleanup();
#endif
}

普通のソケットプログラムだ。mingw のヘッダには sockaddr_un の定義が無いので冒頭で宣言している。Visual Studio だと afunix.h というヘッダが入るはず。Windows はソケットディスクリプタに対して read/write/close が動作しないのはこれまで通りだった。次にクライアントのコード。

#ifdef _WIN32
# include <ws2tcpip.h>
# include <io.h>
#else
# include <sys/fcntl.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netdb.h>
# define closesocket(fd) close(fd)
#endif
#include <stdio.h>

#define UNIX_PATH_MAX 108

typedef struct sockaddr_un {
  ADDRESS_FAMILY sun_family;
  char sun_path[UNIX_PATH_MAX];
} SOCKADDR_UN, *PSOCKADDR_UN;

int
main(int argc, char* argv[]) {
  int client_fd;
  struct sockaddr_un client_addr; 

#ifdef _WIN32
  WSADATA wsa;
  WSAStartup(MAKEWORD(22), &wsa);
#endif

  if ((client_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    perror("server: socket");
    exit(1);
  }

  memset((char *) &client_addr, 0sizeof(client_addr));
  client_addr.sun_family = AF_UNIX;
  strcpy(client_addr.sun_path, "./server.sock");

  if (connect(client_fd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
    perror("client: connect");
    exit(1);
  }

  while (1) {
    char buf[256];
    if (!fgets(buf, sizeof(buf), stdin))
      break;
    char *p = strpbrk(buf, "\r\n");
    if (p) *p = 0;
    if (send(client_fd, buf, strlen(buf), 0) < 0)
      break;
  }

  closesocket(client_fd);

#ifdef _WIN32
  WSACleanup();
#endif
}

こちらも至って普通のコード。一応 UNIX でもコンパイル出来る様にしたつもりだけど、試してはない。

AF_UNIX

mingw でも問題なくコンパイルして動作する様になったし、実行は Cygwin も msys2 も WSL も必要なく動作した。Windows 10 でしか動作しないので、しばらくは OSS で使われる事はないだろうけど、UAC の画面も開かないので個人的には便利だと思う。今後は Windows 版の nginx がリバースプロキシ対応したりするんじゃないかと思う。

Posted at by



2018/04/18


golang の html/template について書かれたブログ等を色々見ていると、みんなレイアウトとコンテンツの分離に苦労している感があったのでどうやるか書いておきます。

t.ExecuteTemplate(w, "content", data)

Go の html/template はテンプレートの名称を指定して ExecuteTemplate を実行します。しかし html/template には、その content を囲う layout テンプレートを指定する方法がありません。特に以下の様に ParseGlob を使った場合、各 html で同じ content という名前で define する事は出来ません。

template.ParseGlob("public/views/*.html")

やりたいのは layout というテンプレートの中から content という名称で読み込まれ、その content に今回実行したいテンプレート、例えば search を適用したいはずです。

{{define "layout"}}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{{.Title}}</title>
</head>
<body>
{{template "content" .}}
</body>
</html>
{{end}}

これを実現するには AddParseTree を使います。

template.Must(template.Must(t.templates.Lookup("layout").Clone()).AddParseTree("content", t.templates.Lookup("search").Tree)).ExecuteTemplate(w, "layout", data)

layout という名称のテンプレート(上記)をクローンし、そのパース済みツリーに content という名前で実行したい別のテンプレート search のパース済みツリーを追加します。これで、layout という名前の一時テンプレートには、content という名前で search の内容が出力される事になります。もちろん ExecuteTemplate に渡すデータには、layout テンプレートで使うタイトル文字列も渡す事になります。

template.Must(template.Must(t.templates.Lookup("layout").Clone()).AddParseTree("content", t.templates.Lookup("search").Tree)).ExecuteTemplate(w, "layout"struct {
    Title  string
    Message string
}{
    Title:   "検索",
    Message: "検索結果はありません",
})

ただしこの方法は、ソースコードに layout という文言が埋め込まれる事になり、content テンプレートの方からレイアウトテンプレートを選ぶ方法がありません。この問題を回避するには、Jekyll のテンプレートの様に、HTML ファイルのヘッダ部に YAML の様なメタ情報を書かせてテンプレート名とレイアウトのペアをプログラムで管理しておき、名称からそのペアを取り出せる仕組みを作っておけば良いですね。

Posted at by



2018/03/30


Go と Vue.js を使ってどれくらいシームレスにウェブアプリを作れるかを確認したかったのでタスク管理アプリを作ってみた。サーバは Go なので vue-cli や webpack 等は使わない。全て CDN から。Vue.js でアプリのベースを、UI コンポーネントとして Element、Ajax ライブラリとして axios を使った。

以前、Riot.js を使って Todo アプリを作った時はサーバ作るのにも少し時間が掛かったので、今回は横着して echo-scaffold を使った。

GitHub - mattn/echo-scaffold

README.md Echo Scaffold Echo Scaffold is CLI to generate scaffolds for the echo framework.

https://github.com/mattn/echo-scaffold

echo-scaffold を使えば、タスク管理アプリとして必要な「本文」と「実施済み」という属性をモデルにした REST サーバを簡単に作る事が出来る。

$ echo-scaffold init go-vue-example
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/helpers/database.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/helpers/errors.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/helpers/response.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/config/database.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/config/environment.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/controllers/router.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/controllers/suite_test.go
        create  /home/mattn/go/src/github.com/mattn/go-vue-example/go-vue-example.go

$ cd go-vue-example
$ echo-scaffold scaffold task body:string done:bool
        create  models/task.go
        create  models/task_dbsession.go
        create  controllers/tasks.go
        create  controllers/tasks_helpers.go
        create  controllers/suite_test.go
        skip  controllers/suite_test.go
        insert  controllers/router.go

モデルとコントローラ、コンフィグや mongodb へのアクセスも生成される。生成された main 関数に少し手を入れて静的ファイルをサーブ出来る様にする。js 側のモデルとのバインディングを書く。

<div id="app">
  <el-main v-loading="loading">
    <h2>My Tasks</h2>
    <el-input placeholder="Please input your task" v-model="newTask" v-on:change="addTask()"></el-input>
    <ul class="list-group">
      <li class="list-group-item" v-for="task in tasks">
        <el-checkbox :checked="task.done" v-on:change="doneTask(task)">{{ task.body }}</el-checkbox>
      </li>
    </ul>
  </el-main>
</div>  
<script src="/static/app.js"></script>

app.js はこんな感じ。

ELEMENT.locale(ELEMENT.lang.ja)
var app = new Vue({
  el: '#app',
  data: {
    tasks: [],
    newTask: "",
    loading: false,
  },
  created: function() {
    this.loading = true;
    axios.get('/tasks')
      .then((response) => {
        console.log(response);
        this.tasks = response.data.items || [];
        this.loading = false;
      })
      .catch((error) => {
        console.log(error);
        this.loading = false;
      });
  },
  methods: {
    addTask: function(task) {
      this.loading = true;
      let params = new URLSearchParams();
      params.append('body'this.newTask);
      params.append('done'false);
      axios.post('/tasks', params)
        .then((response) => {
          this.loading = false;
          this.tasks.unshift(response.data);
          this.newTask = "";
          this.loading = false;
        })
        .catch((error) => {
          console.log(error);
          this.loading = false;
        });
    },
    doneTask: function(task) {
      this.loading = true;
      let params = new URLSearchParams();
      params.append('done'!task.done);
      axios.put('/tasks/' + task.id, params)
        .then((response) => {
          console.log(response);
          this.loading = false;
        })
        .catch((error) => {
          console.log(error);
          this.loading = false;
        });
    } 
  }
})

これだけで簡単にタスク管理アプリが書ける。Vue.js 便利やね。echo-scaffold で生成したコードを一切触ってないし、動作確認は MongoDB Atlas を使ったので、実際の作業は echo-scaffold の実行とフロントエンドのコードを書いたくらい。Element 超良いし axios も良いし、この3点あればだいたいのアプリは作れる。サーバは JSON が吐ける REST サーバであれば何でもいい。

Task管理アプリ

作った物はここに置いた。

GitHub - mattn/go-vue-example
https://github.com/mattn/go-vue-example

個人的な思いとしては Element にもう少し便利な UI が増えてくれればなぁと思う。

Posted at by