2015/02/27


grpc/grpc · GitHub

gRPC - An RPC library and framework

https://github.com/grpc/grpc

gRPC は Google が開発しているRPC(リモートプロシージャコール)のライブラリとフレームワークで、通信層は HTTP/2 を介して行われます。

データ層については、固定されている訳ではなくあくまでデフォルトで Protocol Buffers が使われる様になっています。使用出来るプログラミング言語は現在、C++, Node.js, Python, Ruby, Objective-C, PHP, C# となっています。

実はこれら以外にも grpc-go という、なぜかこのリストに加えられていないオフィシャルリポジトリがあります。

grpc/grpc-go - GitHub

gRPC-Go The Go implementation of gRPC

https://github.com/grpc/grpc-go

今日はこれを使って、gRPC 通信を試してみたいと思います。

まず proto ファイルを作ります。Protocol Buffers の proto ファイルの作り方については過去に何度かやったのと同じです。

proto ファイルは以下の通り。

customer_service.proto
syntax = "proto3";

package proto;

service CustomerService {
  rpc ListPerson(RequestType) returns (stream Person) {};
  rpc AddPerson(Person) returns (ResponseType) {}

}

message ResponseType {
}

message RequestType {
}

message Person {
  string name = 1;
  int32  age  = 2;
}

Protocol Buffers のバージョンが上がり、service の定義も書ける様になりました。定義内容は Person というデータ構造に対して、一覧に追加する AddPerson、一覧を返す ListPerson を定義しました。

この proto ファイルからスタブを作ります。スタブは protoc というコマンドを使うのですが、grpc-go の場合は protoc にデフォルトで組み込まれていない為、protoc のプラグインとして動作させる必要があります。

まず protoc をダウンロードしてきます。Google のサイトに置いてある物は stable release なので今回の service は使えません。ご注意ください。github 上の alpha リリース物をダウンロードします。

Releases - google/protobuf - GitHub
https://github.com/google/protobuf/releases

ダウンロードした protoc をパスの通った所に置いて下さい。

$ go get github.com/golang/protobuf/protoc-gen-go

これで grpc-go が protoc のプラグインとしてインストール出来ます。

protoc --go_out=plugins=grpc:. customer_service.proto

すると、以下の customer_service.pb.go というファイルが生成されます。

// Code generated by protoc-gen-go.
// source: customer_service.proto
// DO NOT EDIT!

/*
Package proto is a generated protocol buffer package.

It is generated from these files:
    customer_service.proto

It has these top-level messages:
    ResponseType
    RequestType
    Person
*/
package proto

import proto1 "github.com/golang/protobuf/proto"

import (
    context "golang.org/x/net/context"
    grpc "google.golang.org/grpc"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto1.Marshal

type ResponseType struct {
}

func (m *ResponseType) Reset()         { *m = ResponseType{} }
func (m *ResponseType) String() string { return proto1.CompactTextString(m) }
func (*ResponseType) ProtoMessage()    {}

type RequestType struct {
}

func (m *RequestType) Reset()         { *m = RequestType{} }
func (m *RequestType) String() string { return proto1.CompactTextString(m) }
func (*RequestType) ProtoMessage()    {}

type Person struct {
    Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
    Age  int32  `protobuf:"varint,2,opt,name=age" json:"age,omitempty"`
}

func (m *Person) Reset()         { *m = Person{} }
func (m *Person) String() string { return proto1.CompactTextString(m) }
func (*Person) ProtoMessage()    {}

func init() {
}

// Client API for CustomerService service

type CustomerServiceClient interface {
    ListPerson(ctx context.Context, in *RequestType, opts ...grpc.CallOption) (CustomerService_ListPersonClient, error)
    AddPerson(ctx context.Context, in *Person, opts ...grpc.CallOption) (*ResponseType, error)
}

type customerServiceClient struct {
    cc *grpc.ClientConn
}

func NewCustomerServiceClient(cc *grpc.ClientConn) CustomerServiceClient {
    return &customerServiceClient{cc}
}

func (c *customerServiceClient) ListPerson(ctx context.Context, in *RequestType, opts ...grpc.CallOption) (CustomerService_ListPersonClient, error) {
    stream, err := grpc.NewClientStream(ctx, &_CustomerService_serviceDesc.Streams[0], c.cc, "/proto.CustomerService/ListPerson", opts...)
    if err != nil {
        return nil, err
    }
    x := &customerServiceListPersonClient{stream}
    if err := x.ClientStream.SendProto(in); err != nil {
        return nil, err
    }
    if err := x.ClientStream.CloseSend(); err != nil {
        return nil, err
    }
    return x, nil
}

type CustomerService_ListPersonClient interface {
    Recv() (*Person, error)
    grpc.ClientStream
}

type customerServiceListPersonClient struct {
    grpc.ClientStream
}

func (x *customerServiceListPersonClient) Recv() (*Person, error) {
    m := new(Person)
    if err := x.ClientStream.RecvProto(m); err != nil {
        return nil, err
    }
    return m, nil
}

func (c *customerServiceClient) AddPerson(ctx context.Context, in *Person, opts ...grpc.CallOption) (*ResponseType, error) {
    out := new(ResponseType)
    err := grpc.Invoke(ctx, "/proto.CustomerService/AddPerson", in, out, c.cc, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Server API for CustomerService service

type CustomerServiceServer interface {
    ListPerson(*RequestType, CustomerService_ListPersonServer) error
    AddPerson(context.Context, *Person) (*ResponseType, error)
}

func RegisterCustomerServiceServer(s *grpc.Server, srv CustomerServiceServer) {
    s.RegisterService(&_CustomerService_serviceDesc, srv)
}

func _CustomerService_ListPerson_Handler(srv interface{}, stream grpc.ServerStream) error {
    m := new(RequestType)
    if err := stream.RecvProto(m); err != nil {
        return err
    }
    return srv.(CustomerServiceServer).ListPerson(m, &customerServiceListPersonServer{stream})
}

type CustomerService_ListPersonServer interface {
    Send(*Person) error
    grpc.ServerStream
}

type customerServiceListPersonServer struct {
    grpc.ServerStream
}

func (x *customerServiceListPersonServer) Send(m *Person) error {
    return x.ServerStream.SendProto(m)
}

func _CustomerService_AddPerson_Handler(srv interface{}, ctx context.Context, buf []byte) (proto1.Message, error) {
    in := new(Person)
    if err := proto1.Unmarshal(buf, in); err != nil {
        return nil, err
    }
    out, err := srv.(CustomerServiceServer).AddPerson(ctx, in)
    if err != nil {
        return nil, err
    }
    return out, nil
}

var _CustomerService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "proto.CustomerService",
    HandlerType: (*CustomerServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "AddPerson",
            Handler:    _CustomerService_AddPerson_Handler,
        },
    },
    Streams: []grpc.StreamDesc{
        {
            StreamName:    "ListPerson",
            Handler:       _CustomerService_ListPerson_Handler,
            ServerStreams: true,
        },
    },
}

あとは golang の開発手順通り、これを参照したサーバとクライアントを作ります。まずサーバ

package main

import (
    "golang.org/x/net/context"
    "log"
    "net"
    "sync"

    "google.golang.org/grpc"

    pb "github.com/mattn/grpc-example/proto"
)

type customerService struct {
    customers []*pb.Person
    m         sync.Mutex
}

func (cs *customerService) ListPerson(p *pb.RequestType, stream pb.CustomerService_ListPersonServer) error {
    cs.m.Lock()
    defer cs.m.Unlock()
    for _, p := range cs.customers {
        if err := stream.Send(p); err != nil {
            return err
        }
    }
    return nil
}

func (cs *customerService) AddPerson(c context.Context, p *pb.Person) (*pb.ResponseType, error) {
    cs.m.Lock()
    defer cs.m.Unlock()
    cs.customers = append(cs.customers, p)
    return new(pb.ResponseType), nil
}

func main() {
    lis, err := net.Listen("tcp"":11111")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    server := grpc.NewServer()

    pb.RegisterCustomerServiceServer(server, new(customerService))
    server.Serve(lis)
}

何の面白味もないフツーに RPC のコードですね。一覧を返す場合は、配列ではなく stream に対して書き込む事で一覧を返す事になります。またどうやら Protocol Buffers では引数の無いインタフェースは作れない様なので、RequestType と ResponseType という型も用意していますが、要らないからといって nil を返したりすると落ちるので要注意です。

次にクライアント

package main

import (
    "fmt"
    "io"
    "strconv"

    pb "github.com/mattn/grpc-example/proto"
    "github.com/mattn/sc"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

func add(name string, age interror {
    conn, err := grpc.Dial("127.0.0.1:11111")
    if err != nil {
        return err
    }
    defer conn.Close()
    client := pb.NewCustomerServiceClient(conn)

    person := &pb.Person{
        Name: name,
        Age:  int32(age),
    }
    _, err = client.AddPerson(context.Background(), person)
    return err
}

func list() error {
    conn, err := grpc.Dial("127.0.0.1:11111")
    if err != nil {
        return err
    }
    defer conn.Close()
    client := pb.NewCustomerServiceClient(conn)

    stream, err := client.ListPerson(context.Background(), new(pb.RequestType))
    if err != nil {
        return err
    }
    for {
        person, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        fmt.Println(person)
    }
    return nil
}

func main() {
    (&sc.Cmds{
        {
            Name: "list",
            Desc: "list: listing person",
            Run: func(c *sc.C, args []stringerror {
                return list()
            },
        },
        {
            Name: "add",
            Desc: "add [name] [age]: add person",
            Run: func(c *sc.C, args []stringerror {
                if len(args) != 2 {
                    return sc.UsageError
                }
                name := args[0]
                age, err := strconv.Atoi(args[1])
                if err != nil {
                    return err
                }
                return add(name, age)
            },
        },
    }).Run(&sc.C{})
}

最近開発中の、サブコマンドライブラリ sc を使ってみました。サーバが stream から Send で送ってくるのでクライアントもループで回しながら Recv する必要があります。

やったね!

ちゃんと動きました。固い型付けの言語でしっかり動くと気持ちいいですね。今後いろいろなアプリケーションが grpc を使い、HTTP/2 が透過的に使われていく時代になっていくのだと思います。ワクワクしますね。

今日作った物は以下のリポジトリに置いてあります。よろしければ遊んで下さい。

mattn/grpc-example - GitHub
https://github.com/mattn/grpc-example

2015/02/23


SPA (Single Page Application) を書いていると、いちいちブラウザをリロードするのが面倒で、かつ js を minify してページをリロードするといった面倒な手間を出来れば何も設定せずにやりたい(もしくは微量な設定だけでやりたい)、という思いから goemon というツールを書きました。

mattn/goemon - GitHub
https://github.com/mattn/goemon

goemon は、コマンドラインツールとして使います。まず

$ goemon -g > goemon.yml

goemon.yml を生成します。個人的にカスタマイズしたい人は生成されたファイルを変更して使って下さい。

# Generated by goemon -g
livereload: :35730
tasks:
match: './assets/*.js'
  commands:
  minifyjs -m -i ${GOEMON_TARGET_FILE} > ${GOEMON_TARGET_DIR}/${GOEMON_TARGET_NAME}.min.js
  :livereload /
match: './assets/*.css'
  commands:
  :livereload /
match: './assets/*.html'
  commands:
  :livereload /
match: '*.go'
  commands:
  go build
  :restart
  :livereload /
$ goemon ./foo

foo が起動します。サーバを起動するコマンドを渡す事になります。デフォルトの設定だと以下の様に動作します。

  • assets/*.js が変更されたら同じディレクトリに minify した js(拡張子: .min.js) を生成し、/ を livereload (自動再読み込み)する。
  • assets/*.htmlassets/*.css が変更されたら / を livereload する。
  • *.go が変更されたら go build してアプリを再起動(引数を再実行)した後、/ を livereload する。

つまりは開発者はエディタで編集するだけでブラウザ勝手にリロードしてウマーーー!という想定

別に golang だけのツールという訳では無いので、ruby や node/io.js 等でも使えます。ワイルカードパターンには **/*.js という複数ディレクトリ階層のワイルドカードも使えます。もちろんですが、Windows でも動作します。詳しくは README を参照下さい。

goemon で使える便利なパターンやコマンドがあれば、ぜひ pull-request して下さい。


2015/02/09


この文章は、http://yannesposito.com/Scratch/en/blog/Vim-as-IDE/ で掲載されている「Vim as IDE」の翻訳文です。

文内の全てはの筆者による物であり、訳文の内容については私による物となります。意訳が若干入っています。間違い等あればご連絡下さい。

Vim as IDE

Main image

tl;dr: 如何に Vim を効率の良い IDE として使いこなすか

Learn Vim Progressively (訳者注: 日本語訳)では Vim が如何にテキスト編集に優れ、かつファイルを操縦できる偉大なテキストエディタであるかについて示した。この短い記事では、私が如何にして Vim を IDE として使っているかについて見る事が出来るだろう。そう、主に幾つかの素晴らしいプラグインを使う事により。


1. Vim Plugin Manager

Vim のプラグインは たくさん プラグインがある。それらを管理するのに私は vim-plug を使っている。

インストール方法:

mkdir -p ~/.vim/autoload
curl -fLo ~/.vim/autoload/plug.vim \
             https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

☞ 私の .vimrc には2つの役割がある。まず一つが私のプラグインの全てが含まれている部分。そして二つ目がそれらのプラグインの為に行っている個人的な設定が含まれている部分。以下のコードではそれを ... で分けて記載する。


2. 生き残る


2.1. カラースキーム

Solarized theme

何よりも前に、読みやすくコントラストの低いカラースキームを使って目を守るべきだ。

その為に私は solarized dark を使っている(訳者注: mattn も使っています)。これを追加するには ~/.vimrc に次の様に追加する必要がある:

call plug#begin('~/.vim/plugged')

Plug 'altercation/vim-colors-solarized'

call plug#end()

" -- solarized personal conf
set background=dark
try
    colorscheme solarized
catch
endtry

2.2. 最低限のエチケット

行の後ろに空白を見かけたら削除しているはずだ。

Trim whitespaces
Plug 'bronson/vim-trailing-whitespace'

:FixWhitespace で余計な空白を掃除出来る。

そしてもう一つ、80カラム問題。

if (exists('+colorcolumn'))
    set colorcolumn=80
    highlight ColorColumn ctermbg=9
endif
80th column

3. File Management

プログラミングにとって密かに重要なスキル、それはプロジェクトの中からファイルを探し、そして見つけ出す能力だ。

巷では NERDTree の様な物が好まれて使われている。これは古典的な左カラムにプロジェクトのファイルをツリー表示する。私はこれを使うのは辞めてしまった し、君たちもそのはずだ。

私は unite に移行した。左カラムは無くなっている。ファイルを見つけるのが速い。だいたい OS X の Spotlight の様に動作する。

まず ag (the silver search) をインストールする。もし ackag のどちも知らなかったのであれば、君の人生はとても良くなるはずだ。これは単純だがとても本質的なツールだ。言うなればホルモン注射をした grep だ。

" Unite
"   depend on vimproc
"   ------------- VERY IMPORTANT ------------
"   you have to go to .vim/plugin/vimproc.vim and do a ./make
"   -----------------------------------------
Plug 'Shougo/vimproc.vim'
Plug 'Shougo/unite.vim'

...

let g:unite_source_history_yank_enable = 1
try
  let g:unite_source_rec_async_command='ag --nocolor --nogroup -g ""'
  call unite#filters#matcher_default#use(['matcher_fuzzy'])
catch
endtry
" search a file in the filetree
nnoremap <space><space> :split<cr> :<C-u>Unite -start-insert file_rec/async<cr>
" reset not it is <C-l> normally
:nnoremap <space><Plug>(unite_restart)

スペースを2回タイプする。ファイルの一覧が現れる。探したいファイルの文字をいくらかタイプしてみよう。選んでリターンをタイプするとビンゴしたファイルが横分割で開くはずだ。

Unite example

もし何か変になったら <space>r をタイプして unite のキャッシュをリセットしよう。

これで簡単にかつ効率的にファイルを名前で検索出来る様になったはずだ。

多くのファイルからテキストを探す。それには ag だ:

Plug 'rking/ag.vim'
...
" --- type ° to search the word in all files in the current dir
nmap ° :Ag <c-r>=expand("<cword>")<cr><cr>
nnoremap <space>/ :Ag

:Ag の後に空白を入れるのを忘れずに。

訳者注: ° は azerty キーボード配列だと装飾キーと1つのキーで押せる物です。日本語キーボードでは別の物で代用すべきです。

この2つはプロジェクトで最もパワフルに働くショートカットだ。° は私の azerty キーボードだととてもいい位置にある。* の近くのキーを使うべきだ。

では ° はどの様に動作するのか?カーソルの下の文字列を読み取り、全てのファイルから検索する。関数がどこで使われているか探すのにとても便利だ。

<space>/ をタイプすると、続く文字列で検索。プロジェクト内の全てのファイルから、そのテキストが現れる部分を検索する。

これでファイル間の操縦がとても簡単に出来る様になったはずだ。


4. 言語にとらわれないプラグイン


4.1. Git

Show modified lines

最後のコミット以降の変更行が見れる。

Plug 'airblade/vim-gitgutter'

And the “defacto” git plugin:

Plug 'tpope/vim-fugitive'

最後のコミットからくわえられた変更は :Gread でリセット出来る。:Gwrite で変更が登録出来る。

Reset changes

4.2. Align things

Plug 'junegunn/vim-easy-align'

...

" Easy align interactive
vnoremap <silent> <Enter> :EasyAlign<cr>

選んで Return、そして space だ。Return を多めにタイプするとアライメントが変更される。

2つ目のコロンで揃えたいなら、Return そして 2、最後に space だ。

Easy align example

4.3. 基本的な自動補完: C-n & C-p

Vim 基本的な自動補完システムを備えている。ショートカットは挿入モードで C-nC-p だ。殆どのケースにおいてこれらは、ほぼ申し分ないくらい良く動作する。例えば言語の構成済みではないファイルを開く場合など。


5. Haskell

私の現在の Haskell プログラミング環境はグレイトだ!

私はファイルを保存するたびに、エラーや、どのように自分のコードを改善すべきかといった提案をコメントに書いている。

だったらこれだ:

cabal install ghc-modghc-mod をインストールするのを忘れるなよ:

" ---------- VERY IMPORTANT -----------
" Don't forget to install ghc-mod with:
" cabal install ghc-mod
" -------------------------------------

Plug 'scrooloose/syntastic'             " syntax checker
" --- Haskell
Plug 'yogsototh/haskell-vim'            " syntax indentation / highlight
Plug 'enomsg/vim-haskellConcealPlus'    " unicode for haskell operators
Plug 'eagletmt/ghcmod-vim'
Plug 'eagletmt/neco-ghc'
Plug 'Twinside/vim-hoogle'
Plug 'pbrisbin/html-template-syntax'    " Yesod templates

...

" -------------------
"       Haskell
" -------------------
let mapleader="-"
let g:mapleader="-"
set tm=2000
nmap <silent> <leader>ht :GhcModType<CR>
nmap <silent> <leader>hh :GhcModTypeClear<CR>
nmap <silent> <leader>hT :GhcModTypeInsert<CR>
nmap <silent> <leader>hc :SyntasticCheck ghc_mod<CR>:lopen<CR>
let g:syntastic_mode_map={'mode''active''passive_filetypes': ['haskell']}
let g:syntastic_always_populate_loc_list = 1
nmap <silent> <leader>hl :SyntasticCheck hlint<CR>:lopen<CR>

" Auto-checking on writing
autocmd BufWritePost *.hs,*.lhs GhcModCheckAndLintAsync

"  neocomplcache (advanced completion)
autocmd BufEnter *.hs,*.lhs let g:neocomplcache_enable_at_startup = 1
function! SetToCabalBuild()
    if glob("*.cabal") != ''
        set makeprg=cabal\ build
    endif
endfunction
autocmd BufEnter *.hs,*.lhs :call SetToCabalBuild()

" -- neco-ghc
let $PATH=$PATH.':'.expand("~/.cabal/bin")

では楽もう!

hlint on save

私はリーダー(訳者注: キープレフィックス) に - を使う。, は本来の仕様方法として良く使うからだ。

  • -ht はカーソル下にあるブロックの型をハイライト表示する。
  • -hT は現在のブロックの型を挿入する。
  • -hh は選択のハイライトを消す。
Auto typing on save

6. Clojure

Rainbow parenthesis

私の仕事上での主言語のは Clojure だ。そして私の現在の vim 環境はまったくもって良い。lein-kibit の自動化に関する統合が足りていない。いつの日か勇気が湧けばやるつもりだ。しかし、原因である Clojure の非常に長い起動時間を考えると、便利な vim プラグインを作ることが出来るのか疑わしい。

実際に虹のカッコを見る事が出来るだろう(デフォルト値は solarized で壊れているけども)。

私は以前は paredit プラグインを使っていた。しかしそれはとても限定的だった。今は vim の概念をより首尾一貫した sexp を使っている。

" " -- Clojure
Plug 'kien/rainbow_parentheses.vim'
Plug 'guns/vim-clojure-static'
Plug 'guns/vim-sexp'
Plug 'tpope/vim-repeat'
Plug 'tpope/vim-fireplace'

...

autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesActivate
autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadRound
autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadSquare
autocmd BufEnter *.cljs,*.clj,*.cljs.hl RainbowParenthesesLoadBraces
autocmd BufEnter *.cljs,*.clj,*.cljs.hl setlocal iskeyword+=?,-,*,!,+,/,=,<,>,.,:
" -- Rainbow parenthesis options
let g:rbpt_colorpairs = [
    \ ['darkyellow',  'RoyalBlue3'],
    \ ['darkgreen',   'SeaGreen3'],
    \ ['darkcyan',    'DarkOrchid3'],
    \ ['Darkblue',    'firebrick3'],
    \ ['DarkMagenta''RoyalBlue3'],
    \ ['darkred',     'SeaGreen3'],
    \ ['darkyellow',  'DarkOrchid3'],
    \ ['darkgreen',   'firebrick3'],
    \ ['darkcyan',    'RoyalBlue3'],
    \ ['Darkblue',    'SeaGreen3'],
    \ ['DarkMagenta''DarkOrchid3'],
    \ ['Darkblue',    'firebrick3'],
    \ ['darkcyan',    'SeaGreen3'],
    \ ['darkgreen',   'RoyalBlue3'],
    \ ['darkyellow',  'DarkOrchid3'],
    \ ['darkred',     'firebrick3'],
    \ ]

Clojure での作業が非常にスムーズになる。コードの任意の部分を eval することができる。別の端末で Clojure REPL を手動で起動しておく必要がある。


7. 最後に

これらが役に立つであろうと願っている。

最後に大事な事を言い忘れたが、もし私の vim の設定を使ってみたいのであれば、ここから得る事が出来る:

github.com/yogsototh/vimrc


実践Vim 思考のスピードで編集しよう! (アスキー書籍) 実践Vim 思考のスピードで編集しよう! (アスキー書籍)
Drew Neil
KADOKAWA / アスキー・メディアワークス / (2014-01-28)
 
発送可能時間: