2017/06/22


要求仕様から工数を出す側から言うと「ブラウザのダウンロード画面に進捗出てるから要らないでしょ」と言いたい所でしたが「出来ないのか」と言われると「出来るもん」と言わざると得ないエンジニア魂。

JavaScript - ブラウザから、ファイルをダウンロードしている途中で、プログレスバーを実装したい。完了したら、プログレスバーを閉じたい。(81363)|teratail

前提・実現したいこと javaScript/HTML/CSSを利用しております。 目的は、ブラウザから、ファイルをダウンロードしている途中で、プログレスバーを実装したい。完了したら、プログレスバーを閉...

https://teratail.com/questions/81363

通常、ブラウザからファイルをダウンロードする際は javascript からは制御できません。サーバからバイト列を JSON で Range っぽく返して最後に data スキームでダウンロードダイアログを出す、といったニッチなテクニックでも事も出来なくないですがブラウザにメモリを保持してしまって大きいファイルだとハングしかねない等の問題が発生します。で、どうやるかというとまずはサーバの処理

package main

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

    "github.com/google/uuid"
    "github.com/labstack/echo"
)

var (
    m = &sync.Map{}
)

// ダウンロードの進捗を JSON で返す
func stat(c echo.Context) error {
    ck, err := c.Cookie("download-progress")
    if err != nil {
        log.Println(err)
        return err
    }
    progress := 0
    v, ok := m.Load(ck.Value)
    if ok {
        if vi, ok := v.(int); ok {
            progress = vi
        }
    }
    return c.JSON(http.StatusOK, &struct {
        Progress int `json:"progress"`
    }{
        Progress: progress,
    })
}

// クライアントにデータを送信しつつ進捗を更新
func download(c echo.Context) error {
    id := uuid.New().String()
    c.SetCookie(&http.Cookie{
        Name:  "download-progress",
        Value: id,
    })
    f, err := os.Open("ubuntu-17.04-server-amd64.iso")
    if err != nil {
        log.Println(err)
        return err
    }
    defer f.Close()
    st, err := f.Stat()
    if err != nil {
        log.Println(err)
        return err
    }
    total := st.Size()
    rest := total
    m.Store(id, 0)

    w := c.Response().Writer
    w.Header().Set("Content-Disposition""attachment")
    w.Header().Set("Content-Length", fmt.Sprint(total))
    for {
        var b [4098]byte
        n, err := f.Read(b[:])
        if err != nil {
            break
        }
        _, err = w.Write(b[:n])
        if err != nil {
            break
        }
        rest -= int64(n)
        m.Store(id, int((total-rest)*100/total))
        if total <= 0 {
            break
        }
    }
    m.Store(id, nil)
    return nil
}

func main() {
    e := echo.New()

    e.GET("/stat", stat)
    e.GET("/download", download)

    e.Static("/""static")
    e.Logger.Fatal(e.Start(":8989"))
}

そしてクライアント側の処理

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>download</title>
<script>
window.addEventListener('load'function() {
  function progress() {
    fetch("/stat"{
      'credentials'"same-origin"
    }).then(function(response) {
      return response.json();
    }).then(function(json) {
      document.querySelector('#progress').textContent = json.progress + "%";
      if (json.progress < 100) setTimeout(progress, 1000);
    })
  }
  document.querySelector('#download').addEventListener('click'function() {
    progress();
    return true;
  });
}false);
</script>
</head>
<body>
    <p>
        <span id="progress"></span>
    </p>
    <a id="download" href="/download">Download</a>
</body>
</html>

ダウンロードが始まったらランダムIDでクッキーを返送し、そのIDでステータスの要求を受け付ける。ダウンロードは細かい単位で行い都度進捗を更新する。こうすればダウンロードが始まれば進捗がパーセンテージで表示され、終了すればタイマーが止まる。プログレスバー表示やダウンロードをキャンセルした際の処理はめんどくさいので実装してないですが分かりますよね。あとダウンロードが終わったら m から破棄しないと何時かサーバがパンクしますよっと。

ダウンロードの進捗表示は出来なくはない。ただ、これだけは言っておきたい。

実装は、仕事でやるならタダじゃない(575)

2017/06/14


Windows で hosts ファイル(C:\Windows\System32\drivers\etc\hosts) を編集するには管理者権限が必要です。またコマンドラインから IP アドレスを変更するのにも管理者権限が必要です。管理者権限で hosts ファイルを編集するにはメモ帳を管理者権限で起動する必要があります。管理者権限でメモ帳を起動する為にはメニューから「メモ帳」を出し、右クリックして「管理者として実行」を選ぶ必要があります。そして実行したメモ帳のメニューから「開く」でファイルを選択します。管理者として開かないのであれば hosts ファイルを右クリックして「送る」等に登録したエディタを単に選べば済む話なのに、随分と手間ですね。

notepad

UNIX だと

$ sudo vi /etc/hosts

とだけタイプすれば良いのにこの手数の多さはちょっとゲンナリします。特にマウスに手を伸ばしたくないからコマンドプロンプトを使っている僕の様な変な人には辛さしかありません。runas というコマンドを使えば sudo の様な事は出来るのですが、これはパスワード入力を要求されます。メモ帳を管理者として起動する場合だと UAC (User Account Control) のダイアログが表示されるだけなのに、いちいちパスワードなんか打ちたくありません。世の中には C言語で実装した物、タスクスケジューラを使って sudo ぽい事する物、powershell を使って実現している物、いろいろありますがそれぞれ難があるし出来ればキビキビ動くのが欲しかったので作りました。

GitHub - mattn/sudo: sudo for windows

README.md sudo for windows Usage C:\>sudo cmd /c dir Then, you'll see the UAC dialog. Tutorials Disp...

https://github.com/mattn/sudo

標準入出力を扱えるので

sudo cmd /c type secret-file.txt > accessible-file.txt

リダイレクトしたり

echo 123 | sudo my-command.exe | more

パイプで繋げたり出来ます。コマンドプロンプト内のコマンド(例: typeecho)は cmd /c echo の様に起動する必要があります。普段は管理者権限が無いと実行出来ない netsh による IP アドレスの変更も簡単。

sudo netsh interface ip add address "ローカルネットワーク" 33.33.33.33 255.255.255.255

やりたかったメモ帳での hosts ファイル編集も

sudo notepad c:\windows\system32\drivers\etc\hosts
と簡単に出来る様になりました。これを実現する為に、ShellExecuteEx という API の Verb 指定に runas を付けて起動するという方法を取ったのですが、この API で起動したプロセスは、それを起動した同じコンソールを共有する事が出来なかったのでコマンドプロンプトから sudo vim を実行できる様にする事は諦めました。一応、sudo -spawn vim foo.txt で新しいコマンドプロンプトを起動するという機能を付けてありますので、どうしても vim じゃないと嫌だという方はそちらをお使い下さい。間違って起動してしまった場合も CTRL-C するとプロセスを終了する仕組みが入っています。NTサービスをコマンドプロンプトから操作する事も出来るのでずいぶんと楽になりました。
ntservice

Windows でコマンドプロンプトで生活してて sudo したいという、かなり絞られたユーザ層に届けばいいなと思います。


2017/06/09


Goでコマンドライン引数と環境変数の両方からflagを設定したい - Qiita

Goで実装したプログラムでオプションをコマンドライン引数から取るには標準の `flag` パッケージを使いますが、値を環境変数からも読みたいことがあります。(特に Docker で動かす場合) htt...

http://qiita.com/sfujiwara/items/f177d85e9c10f4c34fb6

実は結構簡単に出来ます。github.com/namsral/flag に依存したくない場合や github.com/namsral/flag が実は flag 互換で無かった、なんて問題が見付かった場合に使えるハックです。

オリジナルのコード

package main

import (
    "flag"
    "fmt"
)

func main() {
    var age int
    flag.IntVar(&age, "age"20"your age")
    flag.Parse()
    fmt.Println(age)
}

これに5行(importも入れると実際は7行)足します。

package main

import (
    "flag"
    "fmt"
    "os"
    "strings"
)

func main() {
    var age int
    flag.IntVar(&age, "age"20"your age")
    flag.VisitAll(func(f *flag.Flag) {
        if s := os.Getenv(strings.ToUpper(f.Name)); s != "" {
            f.Value.Set(s)
        }
    })
    flag.Parse()
    fmt.Println(age)
}

こうする事で

flagenv

簡単に環境変数から値を上書きできる様になります。