ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

GZIPOutputStream と ByteArrayOutputStream と try with resources

Javatry with resources は途中で return してもリソースの close をしてくれるけど、なんか間違えた。

GZIPOutputStreamByteArrayOutputStream を使って以下のような、データを圧縮してバイト列にして返す処理を書いていました。( InputStream の部分は実際は外部のファイルのストリームとかになると思います)

String input = "hogehogehoge";
InputStream in = new ByteArrayInputStream(input.getBytes());

try (
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(out)))
) {

  String line;
  while ((line = reader.readLine()) != null) {
    writer.write(line);
    writer.newLine();
  }

  return out.toByteArray();

} catch (IOException e) {
  e.printStackTrace();
  throw new RuntimeException(e);
}

すると次の例外が発生。

java.io.EOFException: Unexpected end of ZLIB input stream

これは GZIPOutputStream を閉じていないときに出る例外で、「 try with resources だから閉じられるのでは?」と思いましたが、 toByteArray しているタイミングはまだ処理が try を抜けていないので閉じられていなかったようです。

以下のように変更して解決しました。バイト列を取得するのは try を抜けてから行うようにしました。

String input = "hogehogehoge";
InputStream in = new ByteArrayInputStream(input.getBytes());

ByteArrayOutputStream out = new ByteArrayOutputStream();

try (
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(out)))
) {

  String line;
  while ((line = reader.readLine()) != null) {
    writer.write(line);
    writer.newLine();
  }

} catch (IOException e) {
  e.printStackTrace();
  throw new RuntimeException(e);
}
return out.toByteArray();

感想

すぐ終わると思って書いたコードが動かなくて夜が明けてしまった。

最近景気が悪いので、仕事がなくなったら各地のお祭りを巡る旅とかしたい。

修了証明書

Certificate of Completion

スライドとかでよかった気がするけど、なんとなく html で修了証明書を書いてみました。css は無限に時間を溶かすと思います。

See the Pen Certificate of Completion by ushumpei (@ushumpei) on CodePen.

MacChrome でしか確認していない。もしかしたら他の環境でフォントとかダメかもしれないです。

COURSE DETAILS (リボンの下の細かい文字) が短すぎると良い感じの形にならないです。三文くらいあると印刷した時に A4 サイズにうまくおさまる。

参考

ありがとうございました。

リボン

saruwakakun.com

リボンのコード、border で三角形を作るやつの応用で白い三角形を使って切り口を表現する。

mrtc.jp

画像使わないと縁取りかっこよくならないかと思ったけど、radial-gradientlinear-gradient を使って丸みを出している。ただコードちゃんと追えていない。

感想

効力のない、お気持ちのやつ。というか正式なフォーマットがどこかにあるんだろうな。人に教えるほど何かを極めているわけでもないのにどこへ向かっているんだろうか私は。

スマホで見るとビールのラベルっぽい。

VSCode で Ruby 書くための設定

PC 変えた時に vim & ruby のいい感じの設定が失われてしまいしばらく書かないから放置していたらまた書くことになり VSCode でなんとなく書いてたらいい加減不便さを感じてきたので本当にとりあえずの設定を調べました。(早口)

この辺ができるように設定。

  • 構文チェック
  • 補完と定義ジャンプ

あとこれは

でやってます。

やること

VSCodeRuby の拡張があるので入れる (名前そのまんま Ruby のやつ)

GitHub - rubyide/vscode-ruby: Provides Ruby language and debugging support for Visual Studio Code

拡張を動かすために rubocop があればいいそうなので入れる

gem install rubocop

VSCode の設定ファイルに以下を追加

    "ruby.lint": { "rubocop": true },
    "ruby.useLanguageServer": true,
    "ruby.intellisense": "rubyLocate",

説明

  1. "ruby.lint": { "rubocop": true }: Linter は rubocop
  2. "ruby.useLanguageServer": true: Language Server 使う (構文チェックしてくれるようになる)
  3. "ruby.intellisense": "rubyLocate": インテリセンスの設定 (ある程度、補完、定義ジャンプができるようになる)

終わり

rubyLocate のオプションには定義検索するパスが設定できるようで、うまくやれば結構便利になるかもです?脳死で初期設定できないと割と放置しがちな癖があり、よく無いですね。

open-uri の close とか

rubyopen-uriopen で、サイトをスクレイピングするプログラムを見かけたのだけれど、「close 呼ばなくていいの?」と思って色々調べたのでメモしておきます。結論としては、ブロック渡した時は呼ばなくていいけど基本的には呼ぶ。

調べたのは ruby 2.3 なので古くなるかもです、がそんなに変わらなそうな部分?

ブロック渡した時は呼ばなくていい

open メソッドの実装を見てみます。

> require 'open-uri'
=> true
> method(:open).source_location
=> ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 29]

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L29-L39

  def open(name, *rest, &block) # :doc:
    if name.respond_to?(:open)
      name.open(*rest, &block)
    elsif name.respond_to?(:to_str) &&
          %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
          (uri = URI.parse(name)).respond_to?(:open)
      uri.open(*rest, &block)
    else
      open_uri_original_open(name, *rest, &block)
    end
  end

今回は引数として url 文字列の見渡すので、引数 name は URL として解釈され、URI#open が呼ばれるみたいです。そこだけ切り出して実行してみます。

> (uri = URI.parse('https://ushumpei.hatenablog.com')).respond_to?(:open)
=> true
> uri
=> #<URI::HTTPS https://ushumpei.hatenablog.com>
> uri.method(:open).source_location
=> ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 716]

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L716-L718

OpenURI::OpenRead#open が呼ばれるみたい。その中から OpenURI.open_uri が呼ばれている。

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L132-L166 (抜粋)

    if block_given?
      begin
        yield io
      ensure
        if io.respond_to? :close!
          io.close! # Tempfile
        else
          io.close if !io.closed?
        end
      end
    else
      io
    end

153 行目で block_given? によりブロックが与えられていた時は close を呼ぶように実装されている (ローンパターンか)

なのでブロック渡せば close は明示的に呼ばなくてもいいことがわかりました。

気になったコード

html = open('https://ushumpei.hatenablog.com').read

え、これ絶対 close されないのでは?と思ったので lsof で開きっぱなしなことを確認しました。確認コマンドとその意味は以下の感じになります。

$ lsof -c ruby | grep open-uri | wc -l
  • ruby が開いているファイルを見たいので -c ruby でコマンド名を指定しました。
  • 一時ファイル名に open-uri が付いていたのでそれで絞り込み
  • 行数取得

該当コード実行前の lsof -c ruby | grep open-uri | wc -l0

該当コード実行 (irb は開きっぱなし)

> require 'open-uri'
=> true
> 10.times { |n| open('https://ushumpei.hatenablog.com').read }
=> 10

該当コード実行後の lsof -c ruby | grep open-uri | wc -l10irb を終了するとファイル数は 0 に戻りました。

ブロック渡した時は irb 開きっぱなしの状態でもファイル数は 0 のままです。

(注意: open-uriopen した時に、lsofopen-uri がひっついていている行が出力される、という事象は、ソースコードレベルでは確認しきれていません。ちょっとお粗末になり申し訳ないですが、「多分 open 時に作成された開かれているファイルの行だ」という予想に基づいている、と断っておかなければいけません)

まとめ

  • open(XXX).readopen(XXX) { |f| f.read } とかに変えよう
  • lsof って list open files なんですね。ポート使っているプロセス調べるコマンドかと思っていた。
  • というか linux が全てをファイルという形で抽象化しているアレのアレなのか。(よく知らない)
  • そういえば open って |ls とかでパイプつけたコマンド渡すと実行できたりしてアレらしいので、OpenURI.open_uri 呼んじゃった方がいいっぽい。

追記 2019/04/07

  • 可読性微妙かもだけど、 open(XXX).read の書き換え open(XXX, &:read) もいけるのか、 ruby すごい

静的 html を BASIC 認証付きで雑に公開する方法

雑なメモです (なんかやばかったら教えていただきたいです)

1. 適当なリポジトリを作成する

$ mkdir private
$ cd private
$ git init

2. html ファイルとかを配置する

$ echo '<!DOCTYPE html><html><head><title>private</title></head><body><h1>I am private!</h1></body></html>' > index.html

3. heroku アプリを作成して、 php のビルドパックを追加する。php プロジェクトだと認識させるために composer.json を作成する。

これ、 php じゃないのに php 使う気持ち悪さがあります、多分違う方法ありそう。

$ heroku create
$ heroku buildpacks:set heroku/php
$ composer init

4. .htaccess.htpasswd を作成する

デフォルトの設定で apache を使用するようになっているけど、Procfileweb: heroku-php-apache2 とか書くほうが確実かも

$ echo 'AuthUserFile /app/.htpasswd
AuthType Basic
AuthName "Restricted Access"
Require valid-user' > .htaccess
$ htpasswd -c ./.htpasswd なんか好きなユーザー名

(名前が .ht* のファイルにはアクセス制限かけてるみたいだけど、リポジトリ.htpasswd 含めるのってどうなのだろう)

5. 必要なものステージしてコミットして heroku に push する。

$ git add .
$ git commit -m 'Initial commit.'
$ git push heroku master

6. 終わり

$ heroku open