Havitのキーボード「HV-KB390L」のレビュー

以前キーボードのレビュー記事を書いたのですが、 ( 左右分離型のキーボードMistel Barocco MD600が販売再開されたみたいなので買った - くろの雑記帳 )その後、 Havit社の方からウチのキーボードのレビューも書いて欲しいと依頼を受けました。 会社で1ヶ月間Havitのキーボードを利用してみたので、そのレビューを書きます!

どんなキーボード?

Havitは1998年に中国で創業したPC/モバイル周辺機器のメーカーのようです。 HV-KB390Lはロープロファイルのメカニカルスイッチを採用したコンパクトなキーボードです。LEDの装飾付き。

開封の儀

パッケージはこんな感じです。いたってシンプル。 f:id:kuro_m88:20180507112021j:plain

箱をあけてみました。シンプルでコンパクトなキーボードですね。クセはなさそう。

f:id:kuro_m88:20180507112158j:plain

PCに接続してみました。青く光っています!(写真じゃ分かりづらいか…) f:id:kuro_m88:20180507112840j:plain

光る!

キーボードの光らせ方がデフォルトで7種類用意されています。また、Fn + F12キーを押すと学習モードになり、自分で設定した固定のキーのみを光らせる事も可能。

一番気に入ったモードは以下の動画のやつです。

叩いたキーから波紋状に光が広がっていくのがかっこいいですね。慣れるまではLEDがチカチカするのが気になってタイピングしにくかったですが、数日使っていると光っていても気にならなくなってきます。が、会社で使っていたので通り掛かる人には「なにこれ!?」と声を掛けられます。まぁ目立ちますよね。

使われているキーは?

ESCキーを取り外して撮影してみました。 f:id:kuro_m88:20180507182841j:plain

Kailh社のロープロファイルキースイッチが使われています。青軸です。日本だとメカニカルキースイッチだと、CherryのMXシリーズが一番有名だと思いますが、このキーはCherry MXのキーピッチを低くしたような感じです。 青軸なので、クリック感があってカチャカチャ言うタイプですね。さきほどの動画を音声ありで聞いていただければ音がわかると思います。 Cherryの青軸だったら正直うるさいので会社で使うと隣の人に迷惑かな…?と気にしたりしますが、キーピッチが低いせいもあるのか、Cherryの青軸よりはわりと静かです。会社の雑音の中だとこのキーのクリック音程度では迷惑になるほどの音量ではなさそうです。 キーピッチが低めな事に関しても使いにくいのではないかと思っていたのですが、特に気になりませんでした。仕事でPCはMacBook Proを使っているのですが、あの極薄キーボードに慣れている身からすると十分深いですし、Cherry MXでは深すぎてOリングを挟んでキーピッチを浅くしていたのでちょうどいいです。

使ってみてどうだったか

青軸はゲーム用だから仕事には向かないよね、と敬遠していたところもあったのですが、仕事で使ってみると案外打鍵感が気持ちよくて楽しかったです。作りもしっかりとしていて、押し込んでもたわんだり変なゆがみを感じたりしません。安定感があります。キーを斜めに押し込んだ時に引っかかりを感じるキーボードは雑に高速に打鍵してる時にストレスがたまるのですが、わざと斜めに入力してみてもスムーズでした。 キー配列も特にクセのないUS配列で、矢印キーも独立しているのでメカニカルキーボードデビューしてみたい人におすすめです。(日本語配列のリンクは最後にあります)ゲーミングキーボードとして売られていますが特にゲームにしか向いていないなと思う点もありません。Windows用の専用ソフトを使うとキーの入れ替えやマクロの設定ができるようです。 普段はMISTELのMD600を使っている( 左右分離型のキーボードMistel Barocco MD600が販売再開されたみたいなので買った - くろの雑記帳 参照)のですが、このキーボードはFnキーとの組み合わせで音量や再生中の音楽の一時停止、曲送りなどが操作できて、よく使っているのでこの操作ができないのは不便に感じました。 日本のAmazonで検索してみると( https://amzn.to/2JyszA4 )、7999円(2018/06/11時点)とメカニカルキーボードの中ではお手頃な価格だと思います。

本日紹介したキーボード

公式サイトはこちら: www.prohavit.com

日本で購入する場合はAmazonで買うのが楽かなと。日本語配列も発売されたようです。

Akka Httpの中で使われているFastFutureがおもしろかったので紹介

akka-httpの中で使われている FastFuture が面白かったので紹介します。 Scalaの標準の scala.concurrent.Future と基本的な挙動は同じですが、パフォーマンス面で有利になるような実装になっています。

既存のFutureのどこがパフォーマンス的に不利なのか

Future(123).map(_ * 2).map(_ + 234)

のような処理があった時、123 を生成するスレッドと、その値を2倍するスレッドと、 234 を加算するスレッドが別になる。これは flatMap にも言えて

for {
  a <- Future { calculateA() }
  b <- Future { calculateB(a) }
  c <- Future { calculateC(b) }
} yield c

みたいな処理があった時、 calculateA, calculateB, calculateC 3つのメソッドが別のスレッドで実行される。 map でチェーンするのも、flatMap をチェーンさせる(for文)のも Future が作られるもののこの書き方だと直列に実行されるので別スレッドを割り当てるのはコンテキストスイッチを発生するのとCPUのキャッシュが汚染されるのでパフォーマンス的に無駄がある。 型をあわせるだけの目的であれば Future.successful を使えばスレッドが割り当てられないので型をあわせるだけならこれが使われることが多い。 しかしながら、実は Future#map を呼ぶと内部で別スレッドの割当が行われるので Future.successful を使っていてもスレッドの切り替わりは完全に防げているわけではない。

例えば、 scala.concurrent.Future の実装

  def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { // transform(f, identity)
    val p = Promise[S]()
    onComplete { v => p complete (v map f) }
    p.future
  }

map した中身を処理するときに新たに Future を生成しているのでスレッドの切り替わりが発生している。

akka.http.scaladsl.util.FastFuture

実装

https://github.com/akka/akka-http/blob/master/akka-http-core/src/main/scala/akka/http/scaladsl/util/FastFuture.scala

implicit class EnhancedFuture[T](val future: Future[T]) extends AnyVal {
  def fast: FastFuture[T] = new FastFuture[T](future)
}

このimplicitを使って Future#fast を呼べば FastFuture が作れる。

Future(123).fast
=> FastFuture[Int]

FastFuture の一番のポイントは以下で

private case class FulfilledFuture[+A](a: A) extends Future[A] { ... }
private case class ErrorFuture[+A](a: A) extends Future[A] { ... }

def map[B](f: A ⇒ B)(implicit ec: ExecutionContext): Future[B] =
  transformWith(a ⇒ FastFuture.successful(f(a)), FastFuture.failed)

def transformWith[B](s: A ⇒ Future[B], f: Throwable ⇒ Future[B])(implicit executor: ExecutionContext): Future[B] = {
  def strictTransform[T](x: T, f: T ⇒ Future[B]) =
    try f(x)
    catch { case NonFatal(e) ⇒ ErrorFuture(e) }

  future match {
    case FulfilledFuture(a) ⇒ strictTransform(a, s)
    case ErrorFuture(e)     ⇒ strictTransform(e, f)
    case _ ⇒ future.value match {
      case None ⇒
        val p = Promise[B]()
        future.onComplete {
          case Success(a) ⇒ p completeWith strictTransform(a, s)
          case Failure(e) ⇒ p completeWith strictTransform(e, f)
        }
        p.future
      case Some(Success(a)) ⇒ strictTransform(a, s)
      case Some(Failure(e)) ⇒ strictTransform(e, f)
    }
  }
}

transformWith メソッド内で Future を継承した FulfilledFuture もしくは ErrorFuture であれば Future ではなく Try として実行される。それ以外(ふつうのFuture)であれば通常の Future と同じ挙動をする。

いいこと

Future#fast を呼ぶだけで FastFuture が作れて、これを使うと別スレッドの割り当てが発生しないので直列に実行される場合の Future のチェーンにおいて無駄なコンテキストスイッチが発生せずパフォーマンス面で効率がよい。

わるいこと

サービス層とリポジトリ層で ExecutionContext を分けている場合に意図的に割り当てるスレッドプールを変えたいような場合でもスレッドが切り替わらないのでimplicitで ExecutionContext を渡してあげても意図したとおりにスレッドが変わらない。

scala.concurrent.Futureに取り込まれないのか?

議論はありました。が、 map だけ特別扱いするのはおかしいということになり、取り込まれてはいません。 https://contributors.scala-lang.org/t/upates-to-scala-concurrent-future-wrt-breaking-changes/1281/26

Huawei系Android端末のアプリ内の地図で "google play services are updating" という表示が出たまま地図が表示されない件

Huawei Mate 9を使ってるのですが、気がついたらアプリ内に地図が表示されなくなっていて、代わりに "google play services are updating" という一文だけが表示されていました。P 10, P10 liteでも発生したという話も見かけます。

https://lh3.googleusercontent.com/-wmHAml1-Hs0/Wpy33EYUoVI/AAAAAAAAACc/TACEu7GB1ekDdij-5zcFQ2JB0ZiDEMNtwCL4CGAYYCw/s1600/S80305-105955.jpg ( https://productforums.google.com/forum/#!topic/play/xfLz_9emKs0 より)

対処方法

https://productforums.google.com/forum/#!topic/play/xfLz_9emKs0 ここのフォーラムで議論されている内容が近そうです。 自分の端末の場合は以下のような対応で治りました。

設定 → アプリと通知 → アプリ → Google Play開発者サービス → 無効にする → "google play services are updating" が出ていたアプリを開く → Google Play開発者サービスを更新するか聞かれる → 更新する → 地図が問題なく表示されるようになる

サイトブロッキングが話題なのでDNSブロッキングを実現するための方法を検証してみた

本日、NTTコミュニケーションズNTTドコモNTTぷららの3社はサイトブロッキングを実施するとの方針を発表しましたね。

www.ntt.com www.asahi.com

民間事業者による自主的な取組としてサイトブロッキングを行が行われるという建前のため、もし自分が電気通信事業者で意思決定をする立場にあった場合はこのような何のメリットもなく訴訟リスクしかないような事をやろうとは思いませんし、個人的にも報道されているような手法でのサイトブロッキングは反対の立場です。ただ、技術的にはどのように実現すればよいのか興味があるので検証してみました。

サイトブロッキングを実現するための方法の一つとして、「DNSブロッキング」という手法があるそうです。ISPがユーザに対して提供しているDNSキャッシュサーバでブロックしたいサイトの名前解決をできないようにする手法です。ただこの手法を用いても、ユーザがISPの管理外の 1.1.1.1, 1.0.0.1, 8.8.8.8, 8.8.4.4 のようなpublic DNS serverを使えば回避できてしまうのであまり意味はありません。

DNSブロッキングに対応したDNSサーバを構築する

今回はOSとしてubuntu16.04の上でDNSキャッシュリゾルバであるunboundを構築して設定していきます。

まずはふつうのDNSフルリゾルバを構築する

まずはユーザに提供するようのDNSフルリゾルバを作成します。 aptコマンドを使ってunboundをインストールします。

ubuntu@dns-resolver01:~$ sudo apt install unbound

ubuntu@dns-resolver01:~$ sudo service unbound status
● unbound.service
   Loaded: loaded (/etc/init.d/unbound; bad; vendor preset: enabled)
  Drop-In: /run/systemd/generator/unbound.service.d
           └─50-insserv.conf-$named.conf, 50-unbound-$named.conf
   Active: active (running) since Mon 2018-04-23 06:57:24 UTC; 17min ago
     Docs: man:systemd-sysv-generator(8)
   CGroup: /system.slice/unbound.service
           └─2297 /usr/sbin/unbound

インストールコマンドを1行打つだけでunboundがインストールされて起動されていることがわかりますね。この状態だと外部からのアクセスに応答しないようになっているので、private IP アドレスからの問い合わせには応答するようにしてみましょう。

/etc/unbound/unbound.conf.d/dns-cache.conf というファイルを作成し、以下の内容を記述します。

server:
    interface: 0.0.0.0
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 127.0.0.1/32 allow

完了したら、以下のコマンドで再起動をします。

ubuntu@dns-resolver01:~$ sudo service unbound restart

これで別のホストからのアクセスにも応答できるはずです。別ホストからDNSサーバとして参照してみた時の結果が以下です。 10.55.48.99 というのは今回構築したサーバのIPアドレスです。

ubuntu@ubuntu01:~$ dig +short google.com @10.55.48.99
216.58.197.174

DNSによるサイトブロッキングを実現する

ここからが本題です。特定のサイトへアクセスできないようにするにはどうすればよいのでしょうか。サイトブロッキングの対象とされている anitube.se にアクセスできないようにしてみましょう。まずは特に設定を入れない状態で anitube.se の名前解決をしてみます。

ubuntu@ubuntu01:~$ dig +short anitube.se
104.20.203.27
104.20.202.27

2つのIPアドレスが返ってきましたね。名前解決ができています。これを解決できないようにしたいということです。

Unboundのpython拡張

Unboundにはpythonスクリプトで拡張をする機能があります。ドキュメントを参考にして実装しました。 https://unbound.net/documentation/pythonmod/examples/example0.html

gist.github.com

def filter_domain(qstate, id):
    domain = qstate.qinfo.qname_str.rstrip('.')
    if domain in block_domains:
        qstate.return_rcode = RCODE_NXDOMAIN
        qstate.ext_state[id] = MODULE_FINISHED
    else:
        qstate.ext_state[id] = MODULE_WAIT_MODULE

filter_domainという箇所でクエリで問い合わせられているドメインブラックリストに入っていれば名前解決を続行せずに NXDOMAIN を即答するようになっています。

このスクリプト/etc/unbound/unbound/domain_filter.py に設置します。

Unboundの設定

UnboundのPython拡張をインストールします。

ubuntu@dns-resolver01:~$ sudo apt install python-unbound

/etc/unbound/unbound.conf.d/dns-cache.conf を以下のように書き換えます。

server:
    interface: 0.0.0.0
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 127.0.0.1/32 allow
    module-config: "python validator iterator"
python:
    python-script: "/etc/unbound/domain_filter.py"

ドメインのフィルタ対象の一覧ファイルを /etc/unbound/block_domains.txt に作成します。1行1ドメインで列挙していきます。

anitube.se

unboundを再起動します。

ubuntu@dns-resolver01:~$ sudo service unbound restart

動作確認

ubuntu@ubuntu01:~$ dig anitube.se @10.55.48.99

; <<>> DiG 9.10.3-P4-Ubuntu <<>> anitube.se @10.55.48.99
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 17521
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;anitube.se.                    IN      A

;; Query time: 0 msec
;; SERVER: 10.55.48.99#53(10.55.48.99)
;; WHEN: Mon Apr 23 10:01:15 UTC 2018
;; MSG SIZE  rcvd: 39

anitube.se を名前解決しようとしてもレコードが返ってこなくなりました。 ログを見るかぎり、結果はキャッシュされているようなので毎回実行されるわけではなさそうです。

追記

BINDだとRPZという機能を使えばDNSブロッキングができるらしい。職場の方に教えてもらいました。