読者です 読者をやめる 読者になる 読者になる

react-resizable使った話

pgmot.hatenablog.com

これの続きです。

研究で色々あってreactを使う判断をしました。

研究ではAndroidのセンサログ情報やら開発者が実装した行動認識を行うクラスを可視化して想定通りに行動認識アルゴリズムが動作しているかの調査や、そもそもどういったセンサデータが用いれるかを調べられるWebベースのデバッグツールを作っていました。 本デバッグツールでは様々なセンサデータを可視化することとなるのですが、その際には、グラフなどを自由にサイズ変更できる必要があるかと思います。

そこで STRML/react-resizable: A simple React component that is resizable with a handle. を使いました。 名前の通りリサイズに対応したReactコンポーネントです。

インストール方法とインポート方法はREADMEを参照。

function onResize(e, {element, size}){
  this.setState({
    viewSize: size
  });
}

<ResizableBox width={this.state.viewSize.width} height={this.state.viewSize.height} onResize={onResize}>
  <div>
     Resizable
   </div>
</ResizableBox>

簡単ですね。

ただリサイズできるようにするためのハンドルも設定しないといけません。

    .react-resizable {
      position: relative;
    }
    .react-resizable-handle {
      position: absolute;
      width: 20px;
      height: 20px;
      bottom: 0;
      right: 0;
      background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
      background-position: bottom right;
      padding: 0 3px 3px 0;
      background-repeat: no-repeat;
      background-origin: content-box;
      box-sizing: border-box;
      cursor: se-resize;
    }

cssにこれを書く必要があります。backgroundには 型の画像が仕込まれていてここをドラッグすることでリサイズできるような仕組みです。

これもreact-draggableのときと同じようにコンポーネントのサイズを保持したい場合はlocalStorageなどを活用すればよいでしょう。

this.state = {
  viewSize: {
    width: parseInt(localStorage.getItem("width"), 10) || 400,
    height: parseInt(localStorage.getItem("height"), 10) || 400,
  }
};

function onResize(e, {element, size}){
  this.setState({
    viewSize: size
  });

  localStorage.setItem(`width`, size.width);
  localStorage.setItem(`height`, size.height);
}

<ResizableBox width={this.state.viewSize.width} height={this.state.viewSize.height} onResize={onResize}>
  <div>
     Resizable
   </div>
</ResizableBox>

さらにreact-draggableと連携すればドラッグ可能でリサイズ可能なウインドウっぽい何かが完成します。

// constructorなどで実行
this.state = {
  componentPosition: {
    x: parseInt(localStorage.getItem(`position_x`), 10) || 0,
    y: parseInt(localStorage.getItem(`position_y`), 10) || 0
  },
  viewSize: {
    width: parseInt(localStorage.getItem("width"), 10) || 400,
    height: parseInt(localStorage.getItem("height"), 10) || 400,
  }
};

function onDrag(e, position){
  const {x, y} = position;
  this.setState({
    position: {x, y}
  });
}

function onStop(e, position){
  const {x, y} = position;

  localStorage.setItem("position_x", x);
  localStorage.setItem("position_y", y)
}

function onResize(e, {element, size}){
  this.setState({
    viewSize: size
  });

  localStorage.setItem(`width`, size.width);
  localStorage.setItem(`height`, size.height);
}

<Draggable handle="handle" position={this.state.componentPositon} onDrag={onDrag} onStop={onStop}>
  <div className="handle">
    ここをドラッグ!
  </div>
  <ResizableBox width={this.state.viewSize.width} height={this.state.viewSize.height} onResize={onResize}>
    <div>
       Resizable
     </div>
  </ResizableBox>
</Draggable>

こういうことをしてなんとか修論を乗り越えました。裏ではWebsocketやらReduxやら色々使っているので元気が出たらそのあたりも頑張って記事にします。

golangのparseライブラリで別サーバに向ける方法

我らがParse.comさんが本格的にサービス終了されました。それにより前に作ったinstall数集計golangアプリが動作しなくなりました。

pgmot.hatenablog.com

今回の環境ではOSSのparse-serverへの移行を成功させているので、回避策としては単純なもので、向き先サーバを変えるだけだったけどろくなドキュメントがなくて大変だったので書いときます。

結論

まず結論です。最初のParse Clientの作成部分は以下のようになっています。

// Client インストール数問い合わせのクライアント
type Client struct {
    parseClient parse.Client
}

// NewClient Client生成
func NewClient(applicationID string, restAPIKey string) *Client {
    return &Client{
        parseClient: parse.Client{
            Credentials: parse.RestAPIKey{
                ApplicationID: applicationID,
                RestAPIKey:    restAPIKey,
            },
        },
    }
}

client := parseclient.NewClient(defaultApplicationID, defaultRestAPIKey)

それをこうすれば他の実装を変えなくてもいいです。

// Client インストール数問い合わせのクライアント
type Client struct {
    parseClient parse.Client
}

// NewClient Client生成
func NewClient(applicationID string, restAPIKey string) *Client {
    return &Client{
        parseClient: parse.Client{
            Transport: &http.Transport{
                TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
            },
            Credentials: parse.RestAPIKey{
                ApplicationID: applicationID,
                RestAPIKey:    restAPIKey,
            },
            BaseURL: &url.URL{
                Scheme: "https",
                Host:   "parse.server.example.com",
                Path:   "",
            },
        },
    }
}

client := parseclient.NewClient(defaultApplicationID, defaultRestAPIKey)

Transport についてですが、今回建てたparse-sever側のSSL証明書をLet’s encryptにしていましてその証明書に対応しておらず認証失敗となるので、SSLの確認を回避することでとりあえずの回避となっています。これはちゃんとした鍵を置くとかすれば消してもいいでしょう(未検証)。

ソースレベルのお話

https://github.com/facebookgo/parse/blob/master/parse.go

アクセス先のサーバを決めているのは、この https://github.com/facebookgo/parse/blob/master/parse.go#L198 RoundTrip関数

func (c *Client) RoundTrip(req *http.Request) (*http.Response, error) {
    req.Proto = "HTTP/1.1"
    req.ProtoMajor = 1
    req.ProtoMinor = 1

    if req.URL == nil {
        if c.BaseURL == nil {
            req.URL = &defaultBaseURL
        } else {
            req.URL = c.BaseURL
        }
    } else {
        if !req.URL.IsAbs() {
            if c.BaseURL == nil {
                req.URL = defaultBaseURL.ResolveReference(req.URL)
            } else {
                req.URL = c.BaseURL.ResolveReference(req.URL)
            }
        }
    }

    if req.Host == "" {
        req.Host = req.URL.Host
    }

    if req.Header == nil {
        req.Header = make(http.Header)
    }

    var userAgent string
    if c.UserAgent == "" {
        userAgent = defaultUserAgent
    } else {
        userAgent = c.UserAgent
    }

    req.Header.Add(userAgentHeader, userAgent)
    if c.Credentials != nil {
        if err := c.Credentials.Modify(req); err != nil {
            return nil, err
        }
    }

    res, err := c.transport().RoundTrip(req)
    if err != nil {
        return res, err
    }

    if res.StatusCode > 399 || res.StatusCode < 200 {
        body, err := ioutil.ReadAll(res.Body)
        res.Body.Close()
        if err != nil {
            return res, err
        }

        if len(body) > 0 {
            var apiErr Error
            if json.Unmarshal(body, &apiErr) == nil {
                return res, &apiErr
            }
        }
        return res, &RawError{
            StatusCode: res.StatusCode,
            Body:       body,
        }
    }

    return res, nil
}

最初の部分だけ見れば良い

   if req.URL == nil {
        if c.BaseURL == nil {
            req.URL = &defaultBaseURL
        } else {
            req.URL = c.BaseURL
        }
    } else {
        if !req.URL.IsAbs() {
            if c.BaseURL == nil {
                req.URL = defaultBaseURL.ResolveReference(req.URL)
            } else {
                req.URL = c.BaseURL.ResolveReference(req.URL)
            }
        }
    }

req.URLGetPost などを呼び出す際に渡した http.Request なので基本的にはnilになるはず。 でcことclientのBaseURLの存在を見ています。

defaultBaseURL

   defaultBaseURL = url.URL{
        Scheme: "https",
        Host:   "api.parse.com",
        Path:   "/1/",
    }

こうなっているので同じような構造のものをクライアントに渡せば良いことがわかります。

まとめ

自前のparse-serverではlimitに制限がないのでガンガンAPI Callできていいですね。

golangで書いたスクリプトまとめ

Golangの勉強で書いたコードについて挙げます。

pgmot/imagine

これは画像アップローダーです。短いコードなのでガッと載せます。

package main

import (
    "crypto/rand"
    "encoding/base64"
    "io"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "strings"

    "github.com/gin-gonic/gin"
)

func main() {
    port := os.Getenv("PORT")

    if port == "" {
        log.Fatal("$PORT must be set")
    }

    router := gin.Default()

    router.Static("/", "./images")
    router.POST("/upload", func(c *gin.Context) {
        file, header, err := c.Request.FormFile("image")
        fileExt := filepath.Ext(header.Filename)

        b := make([]byte, 40)
        rand.Read(b)
        randomName := base64.URLEncoding.EncodeToString(b)
        out, err := os.Create("./images/" + randomName + strings.ToLower(fileExt))

        if err != nil {
            log.Print(err)
            c.Err()
        }
        defer out.Close()

        _, err = io.Copy(out, file)
        if err != nil {
            log.Print(err)
            c.Err()
        }

        c.String(http.StatusOK, randomName+strings.ToLower(fileExt))
    })

    router.Run(":" + port)
}

まず、 router には2つのエンドポイントが設定されます。一つは /upload です。これは名前の通りPOSTで画像を受けて実行しているサーバの ./images/ 以下に適当なハッシュをつけて保存するエンドポイントです。 画像を見たいときはおもむろに /<hash>でアクセスすると見れます。ここはStaticとして設定をして実現をしていますが、サーバが画像を返すのは現実的によろしくないので、今後の課題とさせていただきます。

pgmot/hanasu

これはwebsocketでいい感じにチャットできるgolang webアプリです。 実装は、

Go言語によるWebアプリケーション開発

Go言語によるWebアプリケーション開発

これをパクって参考にしてやりました。

pgmot/dokokara

これはアクセス元IPを出すgolang webアプリです。 VPNなどを構築した後に本当にVPN越しに行けているのかの確認用に作りましたが、本当は 診断くん でなんとかしています。

package main

import (
    "fmt"
    "log"
    "net"
    "net/http"
    "os"
)

func doko(w http.ResponseWriter, r *http.Request) {
    var ip string

    ip = r.Header.Get("X-Forwarded-For")
    if ip == "" {
        var err error
        ip, _, err = net.SplitHostPort(r.RemoteAddr)
        if err != nil {
            log.Println("SplitHostPort error: ", err)
            fmt.Fprint(w, "IP: ???\nHost: ???")
            return
        }
    }
    log.Println("IP: ", ip)

    host, err := net.LookupAddr(ip)
    if err != nil {
        log.Println("LookupAddr error: ", err)
        fmt.Fprintf(w, "IP: %s\nHost: ???", ip)
        return
    }
    log.Println("Host: ", host)

    fmt.Fprintf(w, "IP: %s\nHost: %s", ip, host[0])
}

func main() {
    port := os.Getenv("PORT")

    if port == "" {
        log.Fatal("$PORT must be set")
    }

    http.HandleFunc("/", doko)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func doko 内でアクセス元の情報をもとに返してあげる実装になっています。簡単でした。

まとめと感想

Webアプリをガッツリ書くよりもAPIのためとかコマンドとして書くみたいな方が書きやすかった。一時期RailsみたいなフルスタックWAFとして使おうとしたけどなんか違う気がする。

ssh設定

sshはすごい便利ですね。中でも気に入っているのが .ssh/config です。一番好きな設定ファイルです。

知らない人に便利さを伝えるときにここを見ろとするために書きます。

普通のログイン

host1.example.com にアクセスしたいときを想定します。

$ ssh <ユーザ名>@host1.example.com

初回の接続時には、以下のような確認メッセージが出ます。

The authenticity of host 'host1.example.com' can't be established.
RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
Are you sure you want to continue connecting (yes/no)?

yes とすれば良いです。この確認メッセージはサーバの秘密鍵フィンガープリントになるのですが、もしDNSキャッシュポイズニングなどで接続先などが書き換えられて悪意のあるサーバにアクセスさせられかけたときに、あれ?フィンガープリント変わってるけど大丈夫???って聞いてくれる仕組みです。

次にパスワードが問い合わされるので答えてあげましょう。 認証が通れば晴れてログインです。

鍵認証

毎回パスワードを入力するのはセキュリティリスクが高くあまり推奨されません、それに面倒ですね。 そこで一般的には公開鍵認証というのが用いられます。公開鍵暗号の仕組みそのものはwikiに説明を譲ります。

まずは自分の秘密鍵と公開鍵を生成する必要があります。

$ ssh-keygen

すると

Generating public/private rsa key pair.
Enter file in which to save the key (/home/you/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/you/.ssh/id_rsa.
Your public key has been saved in /home/you/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx you@your_host

このように出ます。最初の Enter file in which to save the keyでは鍵の位置が聞かれていますが、デフォルトでいいでしょう。 次の Enter passphrase (empty for no passphrase)Enter same passphrase again では秘密鍵に対するパスワードを問われています。 秘密鍵は名の通り秘密にしなくては行けない鍵で、万が一漏れた際には登録しているサーバに問答無用で入られてしまうので鍵をかけたほうがセキュリティは安全です。 が、面倒な場合はかけなくても良いです。そうした場合は鍵の取扱には十分注意してください。

このコマンドのあと ~/.ssh/id_rsa~/.ssh/id_rsa.pub というファイルが生成されています。 ~/.ssh/id_rsa秘密鍵~/.ssh/id_rsa.pub が公開鍵となります。

サーバに公開鍵を配置すると、自動的に公開鍵認証が用いられます。 サーバサイドの ~/.ssh/authorized_keys に公開鍵を追加します。

$ mv id_rsa.pub .ssh # id_rsa.pubはコピーしてきた公開鍵
$ cd .ssh
$ cat id_rsa.pub >> authorized_keys

.ssh/config

~/.ssh/config というファイルに設定を書くことで接続先に名前をつけたり、接続先固有の設定などができたりします。

例えば上の例をもとにすると、

 Host host1
     HostName host1.example.com
     User <ユーザ名>

こうすると、 ssh host1 だけで、User名とHostNameが設定されます。便利。

以下追記していく

react-draggable使った話

研究で色々あってreactを使う判断をしました。

研究ではAndroidのセンサログ情報やら開発者が実装した行動認識を行うクラスを可視化して想定通りに行動認識アルゴリズムが動作しているかの調査や、そもそもどういったセンサデータが用いれるかを調べられるWebベースのデバッグツールを作っていました。 本デバッグツールでは様々なセンサデータを可視化することとなるのですが、その際には、ツール利用者が自由にグラフ配置できるような仕組みが求められるかと思います。 なにぶん、昨今Androidのセンサは増えていますし、行動認識と一口に言っても様々なパラメータや計算過程の値が跋扈するわけです、大量のグラフを並べたいように並べる仕組みが必要かと思います。

そこで mzabriskie/react-draggable: React draggable component というライブラリを使いました。 名前の通りドラッグ可能にする便利コンポーネントです。

インストール方法と設定については公式READMEをご覧ください。

ドラッグ可能にする

ドラッグ可能にしたい要素を Draggable コンポーネントで囲います。

<Draggable>
  <div>
    ドラッグできるよ
  </div>
</Draggable>

これで div 要素はドラッグ可能になりました。簡単ですね。Reactのコンポーネントでも同様にできます。

ウインドウっぽくしたい

現状の実装だと要素内のどこをドラッグしてもドラッグされます。できればウインドウのUIっぽく上の方だけクリックしたら移動みたいな実装にしたいですね。 それには handle 属性を利用すれば良いです。 handle で指定したclassの要素をドラッグすることで移動ができるようになります。

<Draggable handle="handle">
  <div className="handle">
    ここをドラッグ!
  </div>
  こっちではドラッグできないよ
</Draggable>

位置を保存したい

今回のツールはブラウザベースで開発しているのですがリロードのたびに配置が変わるのは好ましくありません。 そこで位置を保存して再読込の際にはロードされるようにしましょう。

// constructorなどで実行
this.state = {
  componentPosition: {
    x: parseInt(localStorage.getItem(`position_x`), 10) || 0,
    y: parseInt(localStorage.getItem(`position_y`), 10) || 0
  }
};

function onDrag(e, position){
  const {x, y} = position;
  this.setState({
    position: {x, y}
  });
}

function onStop(e, position){
  const {x, y} = position;

  localStorage.setItem("position_x", x);
  localStorage.setItem("position_y", y)
}

<Draggable handle="handle" position={this.state.positon} onDrag={onDrag} onStop={onStop}>
  <div className="handle">
    ここをドラッグ!
  </div>
  こっちではドラッグできないよ
</Draggable>

こうすると、初回は position 属性によって位置が決められる。ドラッグ時には onDrag が呼び出され、 state.position が更新される。 ドラッグ停止時には onStop が呼び出され localStorage への保存が行われる。

おみくじをしましょう

しましょう.

皆さんは各々個人slackをお持ちかと思いますので,ここにそんなこともあろうかと,うまく動くおみくじを用意いたしました.

github.com

動かし方

Deploy to Herokuをついでに配置したので使うと良さそう.

その後は,slackのoutgoing webhookで, https://hogeee.heroku.com/omikuji にoutgoingが飛ぶように設定

f:id:programmerMOT:20170103031619p:plain

あとはslack使うだけ f:id:programmerMOT:20170103031722p:plain

今年もいい年になりそうですね

仕組み

package main

import (
    "log"
    "math/rand"
    "net/http"
    "os"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    port := os.Getenv("PORT")

    if port == "" {
        log.Fatal("$PORT must be set")
    }

    router := gin.Default()

    router.POST("/omikuji", func(c *gin.Context) {
        text := c.PostForm("text")
        log.Printf("%s", text)

        nakami := strings.Split(text, ",")
        rand.Seed(time.Now().UnixNano())
        c.JSON(http.StatusOK, gin.H{
            "text":          nakami[rand.Intn(len(nakami))],
            "response_type": "in_channel",
        })
    })
    router.Run(":" + port)
}

仕組みはかなりシンプルで,Outgoingでアクセスが来る→HTTP Responseの際に結果を返すだけ. 今回はシンプルにするために,カンマ区切りでシャッフルするようにした.

最初のos.GetEnv周りはHerokuでうまく動かすためのおまじない.slackのoutgoingはtextというパラメータにテキストが入っているのでそれを利用すればok.

ちなみに

この程度のシンプルなやつならslackbotでもできる.

f:id:programmerMOT:20170103032117p:plainf:id:programmerMOT:20170103032120p:plain

去年頑張ってbot書いたら,この設定を後輩がしてて悲しい気持ちになったのでその供養をさせていただきました.

今年も頑張りましょう

2016年を振り返る

すでに2017年が来ましたが振り返ります.

記事にしたやつ

Nearbyについて

github.com

github.com

ログを見ると1/2に書いているらしいのでよほど暇だったのでしょう.これについては過去記事にも書きましたね.

pgmot.hatenablog.com

詳細についてそのうち書くみたいなことかいてますが,元気がなくかかれずに一年が経ってしまいました.おそらくコードも古いので一度書き直したほうがいいかもですね.

golangについて

2016年はgolangを頑張った気がします.頑張ったというか手を出したというか.入社後使うことはあるのかしら.

アクセスしてきたクライアントのグローバル側のIPとホストを出すサーバ github.com

Websocketでチャットをできるようにしたサーバ github.com

slackでおみくじできるようにしたサーバ github.com

画像のアップロードとそのホスティングをするサーバ github.com

golang結構書きやすくてよかった.一年に1つ新しい言語を学ぶ姿勢みたいなのがいいと聞いて始めたんだけど長い付き合いになりそう.

line botについて

github.com

pgmot.hatenablog.com

LINE Botがdeveloper previewとして出た次の日ぐらいに書いたbotです.line echoでlinechoという名前になっています. 次の日ぐらいに書いたんですけどやはり最初は取れなかったですね.取ったところで同しようもないのですが. ただ,めちゃくちゃバズってた同じようなLINE echo botの記事で,ここが引っかかるよ!みたいな説明をみて同じのに引っかかった記憶があり,自らの手での体験は重要であるなぁと思った.

それ以外

研究

Android JavaJavaScriptを書いてます.たまのツールでGolang

pgmot.hatenablog.com

お仕事

Rubyと,JavaScript書きました.

pgmot.hatenablog.com

pgmot.hatenablog.com

まとめと雑感

研究でJava書きまくるのはいつもどおりでした.2016年の試みとしては,GolangJavaScript(ReactとかES6とか)が新しいところになるのかな.2017年もこの調子で新しい言語をゆっくり覚えていきたい.

2017年は

Elixir勉強しています.ガンバルゾー