今回は、Go言語で簡単な
ファイルサーバを自作していきます。
機能は画像をアップロード(転送)するだけです。
実際は、画像ファイル以外にも、
実行ファイルやpdfなど各種ファイルをアップロードできます。
使用する機会は多くはありませんが、
既存のシステム(クラウド、AirDropなど)が
使用できない場合などで活躍します。
もしくは誰もが使用する社内PCなどへ
自分の何かかしらのアカウントでログインせずに、
そのままデータ転送したい場合なども有効です。
私はたまに私生活で利用することがあります。
友達のiPhoneから動画などを受け取りたい、
といったときにたまに活躍します。
それでは実際に見ていきましょう!
フォルダの構成は以下のようになっています。
1 2 3 4 5 6 |
directory | |-----data |-----html | |---main.html |-----main.go |
アップロードされたデータは、
dataファルダの中に保存されます。
今後(機能拡張)のこと考えて、
htmlデータはhtmlファルダに入れてあります。
htmlデータは以下のようになっています。
1234567891011121314
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <form action="/recv" method="POST" enctype="multipart/form-data"> <input type="file" name="image" multiple> <input type="submit"> </form></body></html>
input fileとformをセットしただけです。
続いて、main.goです。
サーバ機能とアップロードデータの保存を行っています。
起動時にPCのIPアドレスを取得し、
それをターミナル上に表示してくれます。
対応はIPv4のみです。
192... / 172...とで分けてありますが、
これは動作させるアドレスを保証させるためです。
例えばPCの環境によっては、
ネットワークインタフェースが複数割り当てられています。
最も可能性が高いのは、
リンクローカルアドレス(Link Local Adress)です。
IPv4であれば、169.254.xxx.xxx / 16 のアドレスです。
仮想環境をPC上で作成したりすると、
その環境によっては作成されます。
もしくは仮想マシンのみで、
ネットワーク(192.168...)を形成したことがあると、
192 と 172 から始まるアドレスが混在する可能性があります。
その場合、実マシンが属するネットワークは、
172... で始まっていることかと思います。
IPアドレスが1つしかないよ!って場合は、
わざわざswitch文で分岐させる必要はありません。
以下のコードでは、192.. / 172.. のどちらか、
先にcaseに入った方がサーバのIPアドレスとして指定されます。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
package main import ( "bytes" "fmt" "io/ioutil" "log" "net" "net/http" "os" "path/filepath") const ( defaultMaxMemory = 32 << 20 // 32 MB port = ":8080") func RecvHandler(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(defaultMaxMemory) fhds := r.MultipartForm.File["image"] for _, fhd := range fhds { src, err := fhd.Open() defer src.Close() if err != nil { log.Println(err) return } dst, err := os.Create(filepath.Join("data", fhd.Filename)) defer dst.Close() if err != nil { log.Println(err) return } io.Copy(dst, src) } fmt.Println("Success!") w.Write([]byte("成功!"))} func main() { http.HandleFunc("/recv", RecvHandler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Println("Someone Acessed!") http.ServeFile(w, r, filepath.Join("html", "main.html")) }) var ip_port bytes.Buffer host, _ := os.Hostname() addrs, _ := net.LookupIP(host) for _, addr := range addrs { if ipv4 := addr.To4(); ipv4 != nil { switch ipv4[0] { case 192: myIP := ipv4.String() ip_port.WriteString(myIP) ip_port.WriteString(port) goto out case 172: myIP := ipv4.String() ip_port.WriteString(myIP) ip_port.WriteString(port) goto out } } } out: fmt.Println(ip_port.String()) if err := http.ListenAndServe(ip_port.String(), nil); err != nil { log.Println("falied to start") }}
ちなみに、データのアップロードは、
一度の複数のデータを選択できますので便利です。
以下はコード上で何をやっているのかです。
コードの説明
ListenAndServe()をするために、
自分のIPアドレスを取得します。
自身のインターフェースに尋ねるのではなく、
ネットワーク上のDNSから取得しています。
それがos.Hostname()とnet.LookupIP()の箇所です。
該当ホスト名に割り当てられた全アドレスが返ってきます。
そして分岐によってサーバを立てるアドレスを指定します。
後は、クライントがブラウザ上で、
サーバアドレスを直接指定すればアクセスできます。
クライアントが送信ボタンを押せば、
/recv に画像がアップロード(送信)されます。
サーバ側ではRecvHandler()が動作します。
RecvHandler()では、
Postされてきたデータをパースしています。
MultipartFormで名前フィールドを指定することで、
該当するデータにアクセスできます。
今回がimageをhtml上で指定しまいますので、
引数には"image"を渡してあげます。
ファイルが複数ある場合は、
forループで順に取り出してあげます。
そしてサーバのdataファルダに
1つずつ保存してあげるわけです。
ファイル名はクライアントが、
もともと命名していた名前をそのまま使用します。
もし保存するファイル名変えたければ、
os.Create()の際に指定のファイル名を
引数として渡してあげて下さい。
そして、io.Copy()によって、
パースしたファイルから、
サーバローカルのファイルへ
データをコピーしています。
以上が細かい説明になります。
なお、データはGitHub上にもおいてあります。
今後、機能拡張されれば、GitHub上のデータは
より高機能になっているかもしれません(笑)
最終形態はNASサーバ!
気が向いたら逆方向の、
サーバからデータを抽出する機能を追加しようかな。
最後まで読んでいただきありがとうございました。