20190302のGoに関する記事は17件です。

サーバーレスで定期的にDropbox内のファイルをS3に転送する

つくったもの

  • 定期実行でDropboxの特定フォルダ下ファイルをS3へ転送する機能
  • 具体的には,CloudWatch Eventで定期起動し下記を実行するLambda関数を開発
    1. Dropbox APIを叩いて特定フォルダ配下の全ファイルを取得
    2. 当該ファイルをダウンロードし,S3の特定バケットに保存
    3. バケット保存が成功した場合,Dropbox側のオリジナルファイルを削除
経緯

  • しかしながら,これだけのためにZapierに課金するのも辛いので,イベント駆動を妥協して定期実行でDropbox→S3転送機能を実現してみた
  • これにより,Zapierを経由せず,iPhoneからPixelaをシームレスに結合

Dropbox-S3-Pixela.png

環境

  • MacOS Mojave
  • Go 1.11.5
  • Serverless Framework 1.38.0

ライブラリ選択

前準備

  • 新規Dropboxアプリを登録
  • 作成後,SettingsタブのOAuth 2 - Generated access tokenで"Generate"すると,(テスト向け)トークンを取得可能

開発詳細

先につまづきメモ

  • Dropbox APIでファイル一覧を取得した際の返り値が[]IsMetadataで,ファイル(FileMetadata)として操作できず
    • Goの型アサーションでFileMetadataとして処理が可能に
    • 詳細はこちら
  • Dropboxから取得したファイルコンテンツをS3にわたすため,下記変換手順を踏む必要あり
    • 正直なところGoのio周りやDropboxのDwonload/AWSのReadSeekCloserの仕様を理解していないので,とりあえず[]byteらしきところを引っこ抜いて渡したらうまくいった状態
// download from dropbox
dlArg := files.NewDownloadArg(fpath)
res, content, err := dbx.Download(dlArg)

// extract file content as []byte
blob, _ := ioutil.ReadAll(content)
// []byte -> Reader
f := bytes.NewReader(blob)
// Reader -> aws.ReadSeekCloser
rs := aws.ReadSeekCloser(f)
// S3 PutObject requires aws.ReadSeekCloser as its input body
input := &s3.PutObjectInput{
    Body:   rs,
    Bucket: aws.String(bucketName),
    Key:    aws.String(fileName),
}

開発の初期設定

  • 作ったものはGitHub
  • $GOHOME/src配下の任意フォルダで作業
$ sls create -t aws-go-dep -p <project-name>
  • 生成されたhelloworldの2つの関数は不要なので削除
  • dropbox2s3ディレクトリとmain.goを作成
  • Makefile修正
Makefile
.PHONY: build clean deploy

build:
    dep ensure -v
    env GOOS=linux go build -ldflags="-s -w" -o bin/dropbox2s3 dropbox2s3/main.go

clean:
    rm -rf ./bin ./vendor Gopkg.lock

deploy: clean build
    sls deploy --verbose

  • serverless.yml修正
serverless.yml
service: dropbox2s3-periodic

frameworkVersion: ">=1.28.0 <2.0.0"

provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:ListBucket"
      Resource: "*"
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "*"

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  dropbox2s3:
    handler: bin/dropbox2s3
    events:
      - schedule: cron(0 16 * * ? *)
    environment:
      TZ: Asia/Tokyo
      DROPBOX_TOKEN: <your-api-token>
      IMG_FOLDER_PATH: <target-project-id> 
      BUCKET_NAME: <your-bucket>
    timeout: 60

main関数修正

  • 冒頭の処理をナイーブに実装
dropbox2s3/main.go
package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "io/ioutil"
    "os"

    "github.com/aws/aws-sdk-go/aws/session"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/s3"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
    "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
)

// Handler is our lambda handler invoked by the `lambda.Start` function call
func Handler(ctx context.Context) error {
    // extract env var
    dropboxToken := os.Getenv("DROPBOX_TOKEN")
    imgFolderPath := os.Getenv("IMG_FOLDER_PATH")
    bucketName := os.Getenv("BUCKET_NAME")

    // tansport image from dropbox to s3
    transport(dropboxToken, imgFolderPath, bucketName)

    return nil
}

func transport(dropboxToken, imgFolderPath, bucketName string) {
    // dropbox setting
    config := dropbox.Config{
        Token: dropboxToken,
    }
    dbx := files.New(config)

    // get info under the imgFolderPath folder
    arg := files.NewListFolderArg(imgFolderPath)
    resp, err := dbx.ListFolder(arg)
    if err != nil {
        fmt.Println("err in accesing dropbox folder")
        fmt.Println(err)
        return
    }

    // for each file/folder
    for _, e := range resp.Entries {
        // use type annotation to cast e (IsMetadata) to file (FileMetadata)
        f, ok := e.(*files.FileMetadata)
        if ok {
            // if e is file, download content
            fmt.Println("find file ", f.Name)
            fpath := f.Metadata.PathLower
            dlArg := files.NewDownloadArg(fpath)
            res, content, err := dbx.Download(dlArg)
            if err != nil {
                fmt.Println("err in downloading file")
                fmt.Println(err)
                return
            }

            // and then put it to S3
            err = putToS3(bucketName, res.Metadata.Name, content)
            if err == nil {
                // if successed, remove transported file on dropbox
                deleteFromDropbox(dbx, fpath)
            }
        }
    }
}

func putToS3(bucketName, fileName string, content io.ReadCloser) error {
    // create s3 client
    sess := session.Must(session.NewSession(&aws.Config{
        Region: aws.String("ap-northeast-1"),
    }))
    svc := s3.New(sess)

    // create put-object input
    blob, _ := ioutil.ReadAll(content)
    f := bytes.NewReader(blob)
    rs := aws.ReadSeekCloser(f)
    input := &s3.PutObjectInput{
        Body:   rs,
        Bucket: aws.String(bucketName),
        Key:    aws.String(fileName),
    }

    // put contet to s3
    _, err := svc.PutObject(input)
    if err != nil {
        fmt.Println("err in putting object")
        fmt.Println(err)
        return err
    }

    fmt.Println("put object ", fileName, " to ", bucketName)
    return nil
}

func deleteFromDropbox(dbx files.Client, filepath string) {
    delArg := files.NewDeleteArg(filepath)
    _, err := dbx.Delete(delArg)
    if err != nil {
        fmt.Println("err in downloading file")
        fmt.Println(err)
        return
    }
}

func main() {
    lambda.Start(Handler)
}

デプロイ

  • 以下でデプロイ
    • make deploy = make + sls deploy bash $ cd <project-name> $ make deploy
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go初心者が書くLinux用 圧縮ファイルの展開/解凍コマンド

はじめに

僕はUbuntuユーザなのですが,Linuxを使っているといろんな種類の圧縮ファイルと遭遇します.
例えば
- .gz
- .zip
- .tar
- .tar.gz / .tar.bz2
などがあります.基本的にファイラからファイルをダブルクリックしたら展開/解凍できるのですが,コマンドで展開したい場面も多くあります.しかしながら,圧縮タイプによってコマンドが異なるため,それぞれ展開コマンドを覚える /「tar.gz 解凍 コマンド」などとググる必要があり,空間/時間計算量が増加してしまいます.
コマンド1つで展開できたら便利じゃね?となるのは自然なことで,Golangの勉強がてらそんなコマンドを書いてみました.

注意

  • 勉強し始めて一日で書いたもので,僕はGoに関しては無知です.(:slight_smile:Goroutine:upside_down:)
  • このコマンドはまだ完成していません.
  • Goの導入に関しては sudo apt install golang

何を作ったの?

goaunpack (名前については後述) というコマンドを作りました.
コマンド一つで複数の(異なるタイプの)圧縮ファイル/ディレクトリを展開します.
また,.zip, .tar.gzなどのアーカイブファイルを展開するときのあるあるなのですが,複数ファイルをディレクトリに入れずに圧縮する場合,展開時にカレントディレクトリにファイルが散らばることがあります.goaunpackでは,ディレクトリを自動作成してこれを防ぎます.Goroutineをつかって並列処理でファイル展開を行います(多分).
使用例
- goaunpack archive.zip: .圧縮ファイルの展開
- goaunpack *.tar.gz: 複数の圧縮ファイルの展開
- goaunpack *: 複数の異なるタイプの圧縮ファイルの展開
- goaunpack file: 拡張子のない圧縮ファイルの展開

僕,無知なの? → はいそうです

  • atool

    はい,そんな便利コマンドはもうあります.書いている途中で知りました.これで,代替できます.sudo apt install atoolでインストールできます.goaunpackの名前はatoolのコマンドをもじったものです.
    goaunpackでは並列処理でファイルを展開できます. → atoolでできるかは不明.

    • 展開 aunpack
    • 圧縮 apack
    • 圧縮ファイル内のファイル表示 als
  • archiver

    よくわからんけどなんかGoで書かれたやつ.
    goaunpackは外部コマンドを使っていますが,これはGoだけで書かれているみたいですね.
    もちろん書いている途中で知りました,こっちのほうが便利でしょう.しらんけど.

Go コード

goaunpack.go
package main

import (
    "fmt"
    "flag"
    "sync"
    "strings"
    "os"
    "os/exec"
)


// Global variables
var (
    SupportedExtensions = []string{
        "zip", "tar", "gz", "bz2", "7z",
        "tgz", "tbz", "tar.gz", "tar.bz2"}
    Verbose   = false
    TargetDir = ""
)


func main(){
    verbose    := flag.Bool("v", false, "print messages")
    targetDir  := flag.String("d", "./", "output directory")
    numWorkers := flag.Int("w", 10, "maximum # of parallel")
    // flag.TYPE(option name, default value, help message)
    flag.Parse()
    args := flag.Args()
    Verbose = *verbose
    TargetDir = *targetDir

    // unpack files with ?Goroutine?
    ch := make(chan string)
    wg := sync.WaitGroup{}

    // make workers
    for i:=0; i<*numWorkers; i++{
        wg.Add(1)
        go callUnpacker(&wg, ch)
    }
    // channel ni fileName wo nagasu
    for _, fileName := range args {
        ch <- fileName
    }
    close(ch)
    wg.Wait()
}


// util functions
func getExtension(fileName string) (string) {
    var extension string
    s := strings.Split(fileName, ".")
    extension = s[len(s)-1]
    // check tar.xx
    if len(s) > 1 { 
        if s[len(s)-2] == "tar"{
            extension = "tar"
        }
    }

    // check an extension is supported or not
    isSupported := false
    for _, supExt := range SupportedExtensions {
        if extension == supExt {
            isSupported = true
            break
        }
    }

    // identify non-supported extension using file command
    if !isSupported {
        outCheck, _ := exec.Command("file", "-z", fileName).Output()
        strOutCheck := string(outCheck)
        // check extension
        if strings.Contains(strOutCheck, "bzip2 compressed data"){
            extension = "bz2"
        } else if strings.Contains(strOutCheck, "gzip compressed data") {
            extension = "gz"
        } else if strings.Contains(strOutCheck, "Zip archive data") {
            extension = "zip"
        } else if strings.Contains(strOutCheck, "tar archive") {
            extension = "tar"
        } else {
            extension = "unknown"
        }
    }
    return extension
}


func getTargetDir(fileName string, topChar []string) (string) {
    hasCommonTop := true
    top0 := topChar[0]
    for _, topN := range topChar {
        if top0 != topN {
            hasCommonTop = false
            break
        } 
    }

    var targetDir string
    if hasCommonTop {
        targetDir = TargetDir
    } else {
        s1 := strings.Split(fileName, "/")
        s2 := strings.Split(s1[len(s1)-1], ".")
        header := s2[0]

        // moshi TargetDir no matsubi ga / denai nara / wo
        // tuketasu
        endStr := string(TargetDir[len(TargetDir)-1])
        if endStr == "/"{
            targetDir = TargetDir + header
        } else {
            targetDir = TargetDir + "/" + header
        }
        if _, err := os.Stat(targetDir); os.IsNotExist(err) {
            os.Mkdir(targetDir, 0777)
        }
    }
    return targetDir
}


// unpacker
func callUnpacker(wg *sync.WaitGroup, ch chan string){
    defer wg.Done() // genri ha humei 
    for {
        fileName, isOpen := <- ch
        if !isOpen {
            return
        }

        extension := getExtension(fileName)
        switch extension {
        case "zip":
            unpackZip(fileName)
        case "tar":
            unpackTar(fileName)
        case "gz":
            unpackGz(fileName)
        case "bz2":
            unpackBz2(fileName)
        case "7z":
            unpack7z(fileName)
        default:
            if Verbose {
                fmt.Println("non-supported extension:", extension)
            }
        }
    }
}


// these functions may be replace with templete
func unpackZip(fileName string){
    // check the tar file's structure
    outLook, _ := exec.Command("unzip", "-l", fileName).Output()
    strOutLook := strings.Split(string(outLook), "\n")

    // check the files has common top directory
    var topChar []string
    for _, line := range strOutLook[3:len(strOutLook)-3] {
        block := strings.Split(line, " ")
        if len(block) > 1 {
            top    := strings.Split(block[len(block)-1], "/")
            topChar = append(topChar, top[0])
        }
    }

    outExtract, _ := exec.Command("unzip", "-d", getTargetDir(fileName, topChar), fileName).Output()
    if Verbose {
        fmt.Println(string(outExtract))
    }
}


func unpackTar(fileName string){
    // check the tar file's structure
    outLook, _    := exec.Command("tar", "-tvf", fileName).Output()
    strOutLook  := strings.Split(string(outLook), "\n")

    // check the files has common top directory
    var topChar []string
    for _, line := range strOutLook {
        block := strings.Split(line, " ")
        if len(block) > 1 {
            top    := strings.Split(block[len(block)-1], "/")
            topChar = append(topChar, top[0])
        }
    }

    outExtract, _ := exec.Command("tar", "-xvf", fileName, "-C", getTargetDir(fileName, topChar)).Output()
    if Verbose {
        fmt.Println(string(outExtract))
    }
}

func unpackGz(fileName string){
    fmt.Println("This is gz file: ", fileName)
    out, _ := exec.Command("gzip", "-d", fileName).Output()
    if Verbose {
        fmt.Println(out)
    }
}

func unpack7z(fileName string){
    fmt.Println("7z has not be supported yet", fileName)
}

func unpackBz2(fileName string){
    // fmt.Println("This is bz2 file: ", fileName)
    out, _ := exec.Command("bzip2", "-d", fileName).Output()
    if Verbose {
        fmt.Println(out)
    }
}

インストール

go build goaunpack.go
mv goaunpack /パスの通ったディレクトリ

必要な外部コマンド

sudo apt install file unzip tar gzip bzip2

おわりに

Goの勉強で書いてみました.いつもはPython3を書いているのですが,Goも書きやすい言語ですね(Pythonほどではない).ただ,使っていない変数やモジュールがあるだけでコンパイルエラーになるのはめんどくさいですね.
興味を持っていただけたり,改善点があればコメントしていただけると嬉しいです.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go初心者が書くLinux展開/解凍コマンド

はじめに

僕はUbuntuユーザなのですが,Linuxを使っているといろんな種類の圧縮ファイルと遭遇します.
例えば
- .gz
- .zip
- .tar
- .tar.gz / .tar.bz2
などがあります.基本的にファイラからファイルをダブルクリックしたら展開/解凍できるのですが,コマンドで展開したい場面も多くあります.しかしながら,圧縮タイプによってコマンドが異なるため,それぞれ展開コマンドを覚える /「tar.gz 解凍 コマンド」などとググる必要があり,空間/時間計算量が増加してしまいます.
コマンド1つで展開できたら便利じゃね?となるのは自然なことで,Golangの勉強がてらそんなコマンドを書いてみました.

注意

  • 勉強し始めて一日で書いたもので,僕はGoに関しては無知です.(:slight_smile:Goroutine:upside_down:)
  • 多分,5,6時間で書いたものです.
  • このコマンドはまだ完成していません.
  • Goの導入に関しては sudo apt install golang

何を作ったの?

goaunpack (名前については後述) というコマンドを作りました.
コマンド一つで複数の(異なるタイプの)圧縮ファイル/ディレクトリを展開します.
また,.zip, .tar.gzなどのアーカイブファイルを展開するときのあるあるなのですが,複数ファイルをディレクトリに入れずに圧縮する場合,展開時にカレントディレクトリにファイルが散らばることがあります.goaunpackでは,ディレクトリを自動作成してこれを防ぎます.Goroutineをつかって並列処理でファイル展開を行います(多分).
使用例
- goaunpack archive.zip: .圧縮ファイルの展開
- goaunpack *.tar.gz: 複数の圧縮ファイルの展開
- goaunpack *: 複数の異なるタイプの圧縮ファイルの展開
- goaunpack file: 拡張子のない圧縮ファイルの展開

僕,無知なの? → はいそうです

  • atool

    はい,そんな便利コマンドはもうあります.書いている途中で知りました.これで,代替できます.sudo apt install atoolでインストールできます.goaunpackの名前はatoolのコマンドをもじったものです.
    goaunpackでは並列処理でファイルを展開できます. → atoolでできるかは不明.

    • 展開 aunpack
    • 圧縮 apack
    • 圧縮ファイル内のファイル表示 als
  • archiver

    よくわからんけどなんかGoで書かれたやつ.
    goaunpackは外部コマンドを使っていますが,これはGoだけで書かれているみたいですね.
    もちろん書いている途中で知りました,こっちのほうが便利でしょう.しらんけど.

Go コード

goaunpack.go
package main

import (
    "fmt"
    "flag"
    "sync"
    "strings"
    "os"
    "os/exec"
)


// Global variables
var (
    SupportedExtensions = []string{
        "zip", "tar", "gz", "bz2", "7z",
        "tgz", "tbz", "tar.gz", "tar.bz2"}
    Verbose   = false
    TargetDir = ""
)


func main(){
    verbose    := flag.Bool("v", false, "print messages")
    targetDir  := flag.String("d", "./", "output directory")
    numWorkers := flag.Int("w", 10, "maximum # of parallel")
    // flag.TYPE(option name, default value, help message)
    flag.Parse()
    args := flag.Args()
    Verbose = *verbose
    TargetDir = *targetDir

    // unpack files with ?Goroutine?
    ch := make(chan string)
    wg := sync.WaitGroup{}

    // make workers
    for i:=0; i<*numWorkers; i++{
        wg.Add(1)
        go callUnpacker(&wg, ch)
    }
    // channel ni fileName wo nagasu
    for _, fileName := range args {
        ch <- fileName
    }
    close(ch)
    wg.Wait()
}


// util functions
func getExtension(fileName string) (string) {
    var extension string
    s := strings.Split(fileName, ".")
    extension = s[len(s)-1]
    // check tar.xx
    if len(s) > 1 { 
        if s[len(s)-2] == "tar"{
            extension = "tar"
        }
    }

    // check an extension is supported or not
    isSupported := false
    for _, supExt := range SupportedExtensions {
        if extension == supExt {
            isSupported = true
            break
        }
    }

    // identify non-supported extension using file command
    if !isSupported {
        outCheck, _ := exec.Command("file", "-z", fileName).Output()
        strOutCheck := string(outCheck)
        // check extension
        if strings.Contains(strOutCheck, "bzip2 compressed data"){
            extension = "bz2"
        } else if strings.Contains(strOutCheck, "gzip compressed data") {
            extension = "gz"
        } else if strings.Contains(strOutCheck, "Zip archive data") {
            extension = "zip"
        } else if strings.Contains(strOutCheck, "tar archive") {
            extension = "tar"
        } else {
            extension = "unknown"
        }
    }
    return extension
}


func getTargetDir(fileName string, topChar []string) (string) {
    hasCommonTop := true
    top0 := topChar[0]
    for _, topN := range topChar {
        if top0 != topN {
            hasCommonTop = false
            break
        } 
    }

    var targetDir string
    if hasCommonTop {
        targetDir = TargetDir
    } else {
        s1 := strings.Split(fileName, "/")
        s2 := strings.Split(s1[len(s1)-1], ".")
        header := s2[0]

        // moshi TargetDir no matsubi ga / denai nara / wo
        // tuketasu
        endStr := string(TargetDir[len(TargetDir)-1])
        if endStr == "/"{
            targetDir = TargetDir + header
        } else {
            targetDir = TargetDir + "/" + header
        }
        if _, err := os.Stat(targetDir); os.IsNotExist(err) {
            os.Mkdir(targetDir, 0777)
        }
    }
    return targetDir
}


// unpacker
func callUnpacker(wg *sync.WaitGroup, ch chan string){
    defer wg.Done() // genri ha humei 
    for {
        fileName, isOpen := <- ch
        if !isOpen {
            return
        }

        extension := getExtension(fileName)
        switch extension {
        case "zip":
            unpackZip(fileName)
        case "tar":
            unpackTar(fileName)
        case "gz":
            unpackGz(fileName)
        case "bz2":
            unpackBz2(fileName)
        case "7z":
            unpack7z(fileName)
        default:
            if Verbose {
                fmt.Println("non-supported extension:", extension)
            }
        }
    }
}


// these functions may be replace with templete
func unpackZip(fileName string){
    // check the tar file's structure
    outLook, _ := exec.Command("unzip", "-l", fileName).Output()
    strOutLook := strings.Split(string(outLook), "\n")

    // check the files has common top directory
    var topChar []string
    for _, line := range strOutLook[3:len(strOutLook)-3] {
        block := strings.Split(line, " ")
        if len(block) > 1 {
            top    := strings.Split(block[len(block)-1], "/")
            topChar = append(topChar, top[0])
        }
    }

    outExtract, _ := exec.Command("unzip", "-d", getTargetDir(fileName, topChar), fileName).Output()
    if Verbose {
        fmt.Println(string(outExtract))
    }
}


func unpackTar(fileName string){
    // check the tar file's structure
    outLook, _    := exec.Command("tar", "-tvf", fileName).Output()
    strOutLook  := strings.Split(string(outLook), "\n")

    // check the files has common top directory
    var topChar []string
    for _, line := range strOutLook {
        block := strings.Split(line, " ")
        if len(block) > 1 {
            top    := strings.Split(block[len(block)-1], "/")
            topChar = append(topChar, top[0])
        }
    }

    outExtract, _ := exec.Command("tar", "-xvf", fileName, "-C", getTargetDir(fileName, topChar)).Output()
    if Verbose {
        fmt.Println(string(outExtract))
    }
}

func unpackGz(fileName string){
    fmt.Println("This is gz file: ", fileName)
    out, _ := exec.Command("gzip", "-d", fileName).Output()
    if Verbose {
        fmt.Println(out)
    }
}

func unpack7z(fileName string){
    fmt.Println("7z has not be supported yet", fileName)
}

func unpackBz2(fileName string){
    // fmt.Println("This is bz2 file: ", fileName)
    out, _ := exec.Command("bzip2", "-d", fileName).Output()
    if Verbose {
        fmt.Println(out)
    }
}

インストール

go build goaunpack.go
mv goaunpack /パスの通ったディレクトリ

必要な外部コマンド

sudo apt install file unzip tar gzip bzip2

おわりに

Goの勉強で書いてみました.いつもはPython3を書いているのですが,Goも書きやすい言語ですね(Pythonほどではない).ただ,使っていない変数やモジュールがあるだけでコンパイルエラーになるのはめんどくさいですね.
興味を持っていただけたり,改善点があればコメントしていただけると嬉しいです.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C の共用体メンバーに cgo からアクセスする方法の一例

環境

  • go: 1.8.1 linux/amd64

昔書いたメモの公開なので公開日より環境がかなり古くなりますがご了承ください.

問題の概要

go の仕様上の制限(?)で cgo で C の共用体の各フィールドにフィールド名を用いてアクセスすることができない.

例えば下記のような共用体 “myunion1” を例にする.

typedef union myunion1 {
    int  iv;
    long lv;
} myunion1;

cgo で myunion1 内の iv に C.int 型の値を代入する場合, 例えば単純に下記のような方法ではコンパイルエラーとなる.

var uni C.myunion1
uni.iv = C.int(100) // これは NG

コンパイルすると下記の様なエラーメッセージ.

$ go run cgouniontest.go
# command-line-arguments
./cgouniontest.go:51: uni.iv undefined (type C.myunion1 has no field or method iv)

解決策

共用体変数への C ポインタの変数を作ってポインタ変数の実体にアクセスするという方法を取れば問題を回避できる.

    var uni C.myunion1
    p := (*C.int)(unsafe.Pointer(&uni))
    *p = C.int(2) // 値の代入
    fmt.Printf("uni.iv=%d\n", int(*p)) // 読み出し

より実践的な例

構造体や enum と組み合わせたより実践的な例を載せておきます.
C.mystruct 型の変数 “str” の値を C から参照した場合 (C の関数 “printstruct()”) と cgo から参照した場合 (fmt.Printf() の行) で並べて表示しています.

cgouniontest.go
package main

//#include <stdlib.h>
//#include <stdio.h>
//#include <string.h>
//
//typedef enum {
//    ev1,
//    ev2,
//    ev3,
//    ev4
//} myenum1;
//
//typedef enum {
//    eev1,
//    eev2,
//    eev3,
//    eev4
//} myenum2;
//
//typedef union myunion1 {
//    int  iv;
//    long lv;
//} myunion1;
//
//typedef union myunion2 {
//    myenum1 uv1;
//    myenum2 uv2;
//} myunion2;
//
//typedef struct mystruct {
//    myunion1 u1;
//    myunion2 u2;
//    int sv;
//} mystruct;
//
//void printstruct(mystruct str) {
//    printf("native c: iv=%d, lv=%ld, uv1=%d, uv2=%d, sv=%d\n", str.u1.iv, str.u1.lv, str.u2.uv1, str.u2.uv2, str.sv);
//}
//
import "C"

import (
    "unsafe"
    "fmt"
    "math"
)

func main() {
    var str C.mystruct
    p1i := (*C.int)(unsafe.Pointer(&str.u1))
    p1l := (*C.long)(unsafe.Pointer(&str.u1))
    p2  := (*C.int)(unsafe.Pointer(&str.u2))
    *p1i = C.int(100)
    *p2  = C.ev2
    str.sv = 3
    C.printstruct(str)
    fmt.Printf("cgo     : iv=%d, lv=%v, uv1=%d, uv2=%d, sv=%d\n", int(*p1i), uint64(*p1l), int(*p2), int(*p2), int(str.sv))
    *p1l = C.long(math.MaxInt32 * 4)
    *p2 =  C.eev3
    C.printstruct(str)
    fmt.Printf("cgo     : iv=%d, lv=%v, uv1=%d, uv2=%d, sv=%d\n", int(*p1i), uint64(*p1l), int(*p2), int(*p2), int(str.sv))
}

実行結果は下記の通り. C からも cgo からも同じ結果が得られた.

$ go run cgouniontest.go
native c: iv=100, lv=100, uv1=1, uv2=1, sv=3
cgo     : iv=100, lv=100, uv1=1, uv2=1, sv=3
native c: iv=-4, lv=8589934588, uv1=2, uv2=2, sv=3
cgo     : iv=-4, lv=8589934588, uv1=2, uv2=2, sv=3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C の共用体メンバーへの cgo からのアクセス方法 一例

環境

  • go: 1.8.1 linux/amd64

昔書いたメモの公開なので公開日より環境がかなり古くなりますがご了承ください.

問題の概要

go の仕様上の制限(?)で cgo で C の共用体の各フィールドにフィールド名を用いてアクセスすることができない.

例えば下記のような共用体 “myunion1” を例にする.

typedef union myunion1 {
    int  iv;
    long lv;
} myunion1;

cgo で myunion1 内の iv に C.int 型の値を代入する場合, 例えば単純に下記のような方法ではコンパイルエラーとなる.

var uni C.myunion1
uni.iv = C.int(100) // これは NG

コンパイルすると下記の様なエラーメッセージ.

$ go run cgouniontest.go
# command-line-arguments
./cgouniontest.go:51: uni.iv undefined (type C.myunion1 has no field or method iv)

解決策

共用体変数への C ポインタの変数を作ってポインタ変数の実体にアクセスするという方法を取れば問題を回避できる.

    var uni C.myunion1
    p := (*C.int)(unsafe.Pointer(&uni))
    *p = C.int(2) // 値の代入
    fmt.Printf("uni.iv=%d\n", int(*p)) // 読み出し

より実践的な例

構造体や enum と組み合わせたより実践的な例を載せておきます.
C.mystruct 型の変数 “str” の値を C から参照した場合 (C の関数 “printstruct()”) と cgo から参照した場合 (fmt.Printf() の行) で並べて表示しています.

cgouniontest.go
package main

//#include <stdlib.h>
//#include <stdio.h>
//#include <string.h>
//
//typedef enum {
//    ev1,
//    ev2,
//    ev3,
//    ev4
//} myenum1;
//
//typedef enum {
//    eev1,
//    eev2,
//    eev3,
//    eev4
//} myenum2;
//
//typedef union myunion1 {
//    int  iv;
//    long lv;
//} myunion1;
//
//typedef union myunion2 {
//    myenum1 uv1;
//    myenum2 uv2;
//} myunion2;
//
//typedef struct mystruct {
//    myunion1 u1;
//    myunion2 u2;
//    int sv;
//} mystruct;
//
//void printstruct(mystruct str) {
//    printf("native c: iv=%d, lv=%ld, uv1=%d, uv2=%d, sv=%d\n", str.u1.iv, str.u1.lv, str.u2.uv1, str.u2.uv2, str.sv);
//}
//
import "C"

import (
    "unsafe"
    "fmt"
    "math"
)

func main() {
    var str C.mystruct
    p1i := (*C.int)(unsafe.Pointer(&str.u1))
    p1l := (*C.long)(unsafe.Pointer(&str.u1))
    p2  := (*C.int)(unsafe.Pointer(&str.u2))
    *p1i = C.int(100)
    *p2  = C.ev2
    str.sv = 3
    C.printstruct(str)
    fmt.Printf("cgo     : iv=%d, lv=%v, uv1=%d, uv2=%d, sv=%d\n", int(*p1i), uint64(*p1l), int(*p2), int(*p2), int(str.sv))
    *p1l = C.long(math.MaxInt32 * 4)
    *p2 =  C.eev3
    C.printstruct(str)
    fmt.Printf("cgo     : iv=%d, lv=%v, uv1=%d, uv2=%d, sv=%d\n", int(*p1i), uint64(*p1l), int(*p2), int(*p2), int(str.sv))
}

実行結果は下記の通り. C からも cgo からも同じ結果が得られた.

$ go run cgouniontest.go
native c: iv=100, lv=100, uv1=1, uv2=1, sv=3
cgo     : iv=100, lv=100, uv1=1, uv2=1, sv=3
native c: iv=-4, lv=8589934588, uv1=2, uv2=2, sv=3
cgo     : iv=-4, lv=8589934588, uv1=2, uv2=2, sv=3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS SDK for Go S3バケット基本操作

AWS SDK for Go

公式Doc
https://docs.aws.amazon.com/ja_jp/sdk-for-go/v1/developer-guide/s3-example-basic-bucket-operations.html

S3と接続するインスタンスの生成

import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/service/s3"
)

creds := credentials.NewStaticCredentials("AWS_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY", "")
sess, err := session.NewSession(&aws.Config{
    Credentials: creds,
    Region: aws.String("ap-northeast-1")},
)
svc := s3.New(sess)

バケットの一覧を取得:ListBuckets

result, _ := svc.ListBuckets(nil)
fmt.Println("Buckets:")

for _, b := range result.Buckets {
    fmt.Printf("* %s created on %s\n", aws.StringValue(b.Name), aws.TimeValue(b.CreationDate))
}

バケットの作成:CreateBucket

bucket := "new-bucket-name"
resp, err := svc.CreateBucket(&s3.CreateBucketInput{
    Bucket: aws.String(bucket),
})
fmt.Println(resp) // -> { Location: "http://new-bucket-name.s3.amazonaws.com/" }

バケット内のオブジェクト一覧を取得:ListObjects

bucket := "bucket-name"
resp, err := svc.ListObjects(&s3.ListObjectsInput{Bucket: aws.String(bucket)})

for _, item := range resp.Contents {
    fmt.Println("Name:         ", *item.Key)
    fmt.Println("Last modified:", *item.LastModified)
    fmt.Println("Size:         ", *item.Size)
    fmt.Println("Storage class:", *item.StorageClass)
    fmt.Println("")
}

ファイルのアップロード:Upload

import "github.com/aws/aws-sdk-go/service/s3/s3manager"

bucket := "bucket-name"
filename := "filename"
file, err := os.Open(filename)

defer file.Close()

uploader := s3manager.NewUploader(sess)
_, err = uploader.Upload(&s3manager.UploadInput{
    Bucket: aws.String(bucket),
    Key: aws.String(filename),
    Body: file,
})

ファイルのダウンロード:Download

import "github.com/aws/aws-sdk-go/service/s3/s3manager"

bucket := "bucket-name"
file, err := os.Create("filename")

defer file.Close()

downloader := s3manager.NewDownloader(sess)
numBytes, err := downloader.Download(file,
    &s3.GetObjectInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(item),
    })

バケット間でファイルをコピー:CopyObject

bucket := "bucket-containing-file"
item := "file-to-copy"
other := "bucket-to-file-copied"

_, err = svc.CopyObject(&s3.CopyObjectInput{Bucket: aws.String(other), CopySource: aws.String(source), Key: aws.String(item)})

ファイルの削除:DeleteObject

bucket := "bucket-name"
obj := "file-to-delete"

_, err = svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(obj)})

err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{
    Bucket: aws.String(bucket),
    Key:    aws.String(obj),
}) // errorが帰ってこなければ成功

全てのファイルを削除:DeleteObjects

import "github.com/aws/aws-sdk-go/service/s3/s3manager"

bucket := "bucket-name"

iter := s3manager.NewDeleteListIterator(svc, &s3.ListObjectsInput{
    Bucket: aws.String(bucket),
})

if err := s3manager.NewBatchDeleteWithClient(svc).Delete(aws.BackgroundContext(), iter); err != nil {
    fmt.Println(err)
}

アーカイブされたファイルの復元:RestoreObject

obj := "file-to-restore"
_, err = svc.RestoreObject(&s3.RestoreObjectInput{
    Bucket: aws.String(bucket),
    Key: aws.String(obj), 
    RestoreRequest: &s3.RestoreRequest{Days: aws.Int64(30)}
    })

バケットの削除:DeleteBucket

bucket := "bucket-name"
_, err = svc.DeleteBucket(&s3.DeleteBucketInput{
    Bucket: aws.String(bucket),
})

err = svc.WaitUntilBucketNotExists(&s3.HeadBucketInput{
    Bucket: aws.String(bucket),
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでTwitter認証をまとめてみた

こんにちわ
ゴリラです。

最近Twitter認可機能を実装する必要があって、
全く知見がないのでこの際、仕組みを理解するために記事をまとめてみました。

内容の理解に間違いがありましたら、コメントで教えてくださいm(_ _)m

目的

今回はより気軽にサービスを利用してもらえるように、
普段使っているSNSアカウントでサービスにログインできるすることが目的です。

Twitterだと、診断系サービスを利用する時に、
認可を行ったりすることが多いですね。

image.png

認証と認可について

認証と認可の仕組みについては、こららの記事を参考にしました。

TwitterはOAuthという認可の仕組みを使用しています。
ここで一つポイントですが、「OAuthは認証の仕組み」ではないということです。
OAuthは「認可の仕組み」であり、「認証が目的ではない」です。

今回はTwitterユーザのデータを取得する権限をアプリ側に付与するために、認可の仕組みを実装します。

Twitterでの認可

参考記事で認証・認可の大枠を掴んだどころで、必要なものを整理します。

必要なものは以下2つです。

  • コンシューマキー
  • アクセストークン

今回やりたいのは「Twitterアカウントでアプリケーションにログインする」ことです。
なのでアプリケーション側でログインしようとするユーザの情報を取得して、保存する必要があります。

ただ、どのアプリケーションでも好きにTwitterからユーザ情報をリクエストできるわけではなく、
Twitterにアプリケーションのプロジェクトを登録して、コンシューマキーというのを取得する必要があります。
コンシューマキーは、Twitterがアプリケーションを認識するための一意なものです。
Twitter側がアプリを識別できないと、悪さをするアプリがあった場合特定&対処できないので、
サービスの健全性を維持するためにも必要ですね。

次に、ユーザのリソースを取得するのにまずユーザのアクセストークンを取得する必要があります。
参考記事にあったように、アクセストークンはを用いることで、ユーザのリソースを取得することができます。

公式のドキュメントを見ると、アクセストークンを取得するまでの流れは以下になります。

  1. callback urlconsumer api keyPOST https://api.twitter.com/oauth/request_tokenして、リクエストトークン(oauth_tokenoauth_token_secret)とoauth_callback_confirmedを取得する。HTTPステータスコードが200かどうかを確認する。
  2. oauth_callback_confirmed がtrueになっていれば、oauth_token, oauth_token_secret をセッションに保存する。
  3. GET https://api.twitter.com/oauth/authorize?oauth_token={{oauth_token}}で認可(未ログインの場合はログイン画面)に遷移する。
  4. 認可が完了したら、oauth_tokenoauth_verifierを含んだ状態で、callback_urlにリダイレクトされる。
  5. 4で帰ってきたoauth_tokenと2でセッションに保存したoauth_tokenを比較して、同じであることを確認する。
  6. consumer api keyoauth_tokenoauth_verifierを含んでPOST https://api.twitter.com/oauth/access_tokenでアクセストークン(oauth_tokenoauth_token_secret)を取得する。

Twitterにアプリを登録する方法

こちらの記事を参考にしてみてください。
ぼくの場合は一瞬で許可降りました。
ユーザのプロフィールしか使わないからかな…?

アプリでの大まかの流れ

フロントはSPAでvue.jsを使っています。
vue.jsだけでOAuth認可だと、アプリのコンシューマキーをサーバーから取得する必要があり、漏れるリスクが増えます。
サーバーだけで処理を完結したいのですが、フロントでTwitterの認可画面に遷移する必要があるので、以下の様に実装してみました。

  1. ログインボタンを押して、認可APIをコールする
  2. 認可API内で1、2を処理して、Twitter認可画面のURLを返却する
  3. 2で受け取った認可URLをjs側で画面遷移させる(location.herf)
  4. ユーザが認可ボタンをポチッと押して4の通り予め設定したcallback_urlに遷移する
  5. callback_url自体はAPIのエンドポイントにして、5、6の処理を行い、アクセストークンを取得する
  6. アクセストークンをDBなりセッションなりに保存して、画面側に200ステータスコードを返してログイン後の画面に遷移する

実装

今回API側の実装に用いたフレームワークは以下になります。

  • Echo 軽量ウェブフレームワーク
  • Gorm ORMフレームワーク
  • oauth Oauthライブラリ

そして、サンプルはこちらです。
ソースを見て頂ければ、大体わかると思いますのでソースに関する説明は省かせていただきます。
https://github.com/skanehira/vue-go-oauth

サンプルはこんな感じです。
twitter-oauth.gif

まとめ

Twitterの認証・認可について大まかの流れは理解しましたが、
トークンとシークレットを必要とする意味など、理解しきれていないので、
引き続きそこらへんを勉強していきます。

この記事は、GoでTwitterの認証認可を実装したいけどよくわからない方に役に立てたらと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang + AWS APIGateway & Lambda & DynamoDB + TwitterAPIでサーバーレスなログイン機能

概要

・Go言語を触りたい
・AWS Lamda & APIGateway使いたい
・TwitterAPI使ってみたい
という願望があったので、全てをまとめてサーバーレスでTwitterログインをできる環境を作ってみた。

結論

実際に稼働させるにはまだまだ課題があるものの、一通り動いてくれるものができた。
AWSもAPIも楽しいので、もっと遊んでいきたい。

できたページ
https://mb8mab272h.execute-api.us-east-2.amazonaws.com/twimal/login
リポジトリ(不要なものが多いため、そのうち作り直します。。)
https://github.com/CHooooCLate/protoTwiM/tree/master/tweet_src

機能

・Twitterログイン
・CookieとSessionによるログイン状態の保持
・ログアウト

実装内容

共通

APIGateway

リクエスト

URLクエリ文字列パラメータを使いたい場合、「メソッドリクエスト / URLクエリ文字列パラメータ」で名前を設定、「統合リクエスト / マッピングテンプレート」に下記のように記載します。

URLクエリ文字列パラメータのマッピングテンプレート
{
    "oauth_token": "$input.params('oauth_token')",
    "oauth_verifier": "$input.params('oauth_verifier')"
}

スクリーンショット 2019-02-21 14.38.07.png
スクリーンショット 2019-02-21 14.38.47.png
スクリーンショット 2019-02-22 10.18.16.png

Cookieの内容はリクエストとして送られてくるため、「メソッドリクエスト / HTTPリクエストヘッダー」で「Cookie」を指定する。
さらに、「統合リクエスト / HTTPヘッダー」で名前に「Cookie」、マッピング元に「method.request.header.Cookie」を指定する。
これをLambda上のGoで扱えるよう、マッピングテンプレートに「application/json」を指定して下記を記載する。

Cookieのマッピングテンプレート
{
    "Cookie": "$input.params().header.Cookie"
}
レスポンス

Lambdaからのレスポンス方法は後述しますが、Lambdaのレスポンスで指定した名前で扱います。
APIGatewayの「統合レスポンス / ヘッダーのマッピング」で「レスポンスヘッダー」にレスポンスヘッダーで使いたい名前、「マッピングの値」に「integration.response.body.(Lambdaで指定した名前)」を指定します。
これによって、レスポンスヘッダーにLcationでリダイレクト先のURLの指定やSet-CookieでCookieの指定などができるようになります。
スクリーンショット 2019-02-21 19.05.19.png
スクリーンショット 2019-02-21 19.05.36.png

また、Lambdaの出力結果にHTMLを含ませたい場合には、「統合レスポンス / マッピングテンプレート」に「application/json」を指定して下記を指定します。

マッピングテンプレート
#set($inputRoot = $input.path('$'))

$inputRoot

さらに、「メソッドレスポンス / 200のレスポンス本文」の「コンテンツタイプ」は「text/html」、「モデル」に「Empty」を指定することでHTMLとして返ります。
スクリーンショット 2019-02-21 19.06.07.png

Lambda

以下に気をつけてLambda関数を作成します。
・ランタイムは「Go 1.x」
・ディレクトリごとzip化した場合は、ハンドラは「ディレクトリ名/コンパイル後のファイル名」とする。
・DynamoDBを操作する関数なら、実行ロールにはDynamoDBの操作権限を持ったロールを指定する。(なければ新たに作成)
また、.envなど、ソース以外のファイルを入れることも可能です。この場合、絶対パスは「/var/task/」以下になります。
Goでレスポンスを指定する場合、下記のように構造体を作成して返り値とします。
APIGatewayで使用する場合には、json:"name"の部分の「name」がキーになります。

レスポンスの構造体例
type Response struct {
    Location string `json:"location"`
    Cookie string `json:"cookie"`
}

スクリーンショット 2019-02-21 19.06.55.png

DynamoDB

下記2つのテーブルを作成した。
・TwitterAPI利用時の一時トークンを格納するTokenテーブル(id, oauth_token, secret_token, register_date)
・ユーザーごとのアクセストークンを格納するSessionテーブル(id, access_token, secret_token, register_date)
各テーブルのインデックスがidに指定されており、このidを使って呼び出している。
スクリーンショット 2019-02-21 19.07.51.png

ログイン

login

ソース : https://github.com/CHooooCLate/protoTwiM/blob/master/tweet_src/login.go
・TwitterAPI利用申請時に取得したCONSUMER_KEYとCONSUMER_SECRETを使用してTwitterAPIから一時トークンを取得
・一時トークンを使用して、TwitterAPIからログインページのURLを取得
・上記で使用した一時トークンをセッションとしてDynamoDBへ保存
・セッションのIDをCookieとしてユーザーに保存
・ログインページのURLへリダイレクト

callback

ソース : https://github.com/CHooooCLate/protoTwiM/blob/master/tweet_src/callback.go
・loginで使用した一時トークンをCookieのidを元にDynamoDBから取得し、URLクエリ文字列パラメータとして渡される一時トークンと同じであることを確認する
・一時トークンとURLクエリ文字列パラメータに含まれる認証用トークンを使用して、ユーザーのアクセストークンをTwitterAPIから取得する
・アクセストークンをDynamoDBへ保存する
・アクセストークンに紐付けたセッションIDをCookieとしてユーザー側に保存
・マイページへリダイレクト

マイページ

mypage

ソース : https://github.com/CHooooCLate/protoTwiM/blob/master/tweet_src/mypage.go
・アクセストークンをCookieのidを元にDynamoDBから取得
・アクセストークンを使用して、ユーザー情報を取得する
・特定の項目(ここでは「screen_name」のみ)を取り出して、HTMLを返す
・セッションIDを新たに作り直し、DynamoDBとCookieを更新

ログアウト

logout

ソース : https://github.com/CHooooCLate/protoTwiM/blob/master/tweet_src/logout.go
・Cookieからidを取得
・DynamoDBから対象のアクセストークンを削除
・ユーザーのCookieを削除

まとめ

当初は「AWS APIGateway + Lambda + DynamoDBでログイン機能」を考えていたが、すでにnode.jsで実現した記事が存在したため、TwitterAPIを取り入れた。
結果としては、予定していた機能は一通り実装でき、AWSとAPIを使ったことで見た目としては十分なものができた。
ただ、同じ処理を何回も書いてしまっているという点では不十分だったため、今後の課題としてはコードの設計をより強化して行きたいと考えている。

参考

【PHP】新TwitterOAuthでログイン機能を実装する
Go言語でGoogle,Twitter,FacebookのOauth認証をしてメールアドレスを取得するまで
GolangでTwitterのOAuth1.0認証
【PHP超入門】Cookieとセッションについて
API GatewayでCookieを扱う
気楽にDynamoDBを使おう
AWS Lambdaにクエリ文字列(get値)を引数で渡すトリガを設定する
AWS Lambda, API Gateway, DynamoDB, Golang でREST APIを作る

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでスタティックバイナリにするオプション

備忘録
https://github.com/golang/go/issues/26492#issuecomment-435462350

We build golang binaries across a variety of platforms, preferring static builds wherever possible. Here are the flags we're currently using:

windows: -tags netgo -ldflags '-H=windowsgui -extldflags "-static"'
linux/bsd: -tags netgo -ldflags '-extldflags "-static"'
macos: -ldflags '-s -extldflags "-sectcreate __TEXT __info_plist Info.plist"'
android: -ldflags -s

On macos and android, we need to be able to pull in system frameworks and libraries so we opted not to build static binaries.

Linuxの例

$ go install -tags netgo -ldflags '-extldflags "-static"'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker SDK for Goでコンテナのログをtailしてみる

こんにちわ

最近DockerのTUI Clientを作っていて、SDKでハマっていたところがあるので、
自分の備忘録として記事に残します。

2019/03/02 追記
HTTP/2の多重化ストリームと書きましたが、
Dockerは独自のフレームを定義してるのでHTTP/2ではなかったです。すいません。

Docker SDKとは

普段Dockerを使っている方なら知っていると思いますが、
Dockerコマンドは実はDocker Engine APIを叩いているだけです。
そのAPIを叩く部分をラッピングして使いやすくしたのでSDKです。

image.png

公式SDKはGoとPythonしかない様ですが、REST APIなのである程度自作はできると思います。
https://docs.docker.com/develop/sdk/

本日は、GoのDocker SDKを使ったコンテナログをtailする時に躓くポイントについて話していきます。

コマンドでコンテナログをtailする

普通にコマンドを実行するとこんな感じになります。

$ docker container logs -f mysql
Initializing database
2019-02-07T03:53:08.512874Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2019-02-07T03:53:09.514164Z 0 [Warning] InnoDB: New log files created, LSN=45790
2019-02-07T03:53:09.647195Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2019-02-07T03:53:09.716490Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: e24f13bf-2a8b-11e9-897a-0242ac180003.
2019-02-07T03:53:09.718579Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2019-02-07T03:53:09.719975Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
2019-02-07T03:53:12.975444Z 1 [Warning] 'user' entry 'root@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.975490Z 1 [Warning] 'user' entry 'mysql.session@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.975499Z 1 [Warning] 'user' entry 'mysql.sys@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.976376Z 1 [Warning] 'db' entry 'performance_schema mysql.session@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.976406Z 1 [Warning] 'db' entry 'sys mysql.sys@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.976691Z 1 [Warning] 'proxies_priv' entry '@ root@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.977023Z 1 [Warning] 'tables_priv' entry 'user mysql.session@localhost' ignored in --skip-name-resolve mode.
2019-02-07T03:53:12.977056Z 1 [Warning] 'tables_priv' entry 'sys_config mysql.sys@localhost' ignored in --skip-name-resolve mode.
Database initialized
Initializing certificates
Generating a RSA private key
...

これをSDKを使って同じ事をします。

SDKを使った場合

SDKではContainersLogsって関数が用意されていて、
それにcontext、コンテナID、オプションを渡すと、レスポンスが返ってきます。
そのレスポンスはdocker側が用意しているdocker/docker/pkg/stdcopy.StdCopyを使用して、標準・エラーに出力します。

package main

import (
    "context"
    "log"
    "os"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
    "github.com/docker/docker/pkg/stdcopy"
)

func run() int {
    c, err := client.NewClientWithOpts(client.WithVersion("1.39"))
    if err != nil {
        return 1
    }

    r, err := c.ContainerLogs(context.Background(), "657814ba450f", types.ContainerLogsOptions{
        ShowStdout: true,
        ShowStderr: true,
        Follow:     true,
    })
    if err != nil {
        log.Println(err)
        return 1
    }

    _, err = stdcopy.StdCopy(os.Stdout, os.Stderr, r)
    if err != nil {
        log.Println(err)
        return 1
    }

    return 0
}

func main() {
    os.Exit(run())
}

ソースはこれだけですが、ちょっとストリーム多重化も関連してくるため、
その知識がないとつまずく方もいるかと思いますので、つまずきポイントについて解説していきます。

躓きポイント

ContainerLogsを使用すると多重化ストリームのレスポンスが返ってきます。
コンテナへのアタッチもこの原理で動いています。

多重化ストリームとは一つのコネクション上で、
複数のHTTPリクエスト&レスポンスのやり取りを行うことができる仕組みとしてHTTP/2で導入されたものです。
詳しくはこちらの資料にわかりやすくまとめてあるので、興味ある方は読んでみてください。

すいません、間違えました。
Dockerでは自前で多重化ストリームを定義していました。
したがってHTTP/2は関係ないです。
ただ、多重化ストリームの考え方同じなはずなので、図のイメージで問題ないと思います。

image.png

Docker側で定められたフレーム定義に従って、
レスポンスを適切に処理して標準・エラー出力しないと、
正しい文字列を得られなくて、先頭に変な文字がついてたりします。

Y2019-03-01T07:14:51.672442Z 0 [Note] InnoDB: 32 non-redo rollback segment(s) are active.
Z2019-03-01T07:14:51.673425Z 0 [Note] InnoDB: 5.7.24 started; log sequence number 12440262
E2019-03-01T07:14:51.673943Z 0 [Note] Plugin 'FEDERATED' is disabled.
g2019-03-01T07:14:51.674929Z 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
2019-03-01T07:14:51.717753Z 0 [Note] Found ca.pem, server-cert.pem and server-key.pem in data directory. Trying to enable SSL support using them.
N2019-03-01T07:14:51.728152Z 0 [Warning] CA certificate ca.pem is self signed.
U2019-03-01T07:14:51.730623Z 0 [Note] Server hostname (bind-address): '*'; port: 3306
82019-03-01T07:14:51.730701Z 0 [Note] IPv6 is available.
@2019-03-01T07:14:51.730720Z 0 [Note]   - '::' resolves to '::';
H2019-03-01T07:14:51.730740Z 0 [Note] Server socket created on IP: '::'.

Docker側が定めたフレーム定義はこちらになります。

header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}

STREAM_TYPEは

  • 0:stdin
  • 1:stdout
  • 2:stderr

となっています。
1なら、os.Stdoutに出力すると言った制御が必要です。

続いて、SIZE1, SIZE2, SIZE3, SIZE4はビッグエンディアン1としてコンコードされた4バイトデータです。
こちらは、ストリームのフレーム(データの部分)の長さになります。

公式SDKでは以下のロジックで正しくデータを取得できると書いてあります。

  1. Read 8 bytes.
  2. Choose stdout or stderr depending on the first byte.
  3. Extract the frame size from the last four bytes.
  4. Read the extracted size and output it on the correct output.
  5. Goto 1.

上記のロジックに従って、自前で制御するとこんな感じになります。

hdr := make([]byte, 8)
for {
    _, err := r.Read(hdr)
    if err != nil {
        log.Fatal(err)
    }
    var w io.Writer
    switch hdr[0] {
    case 1:
        w = os.Stdout
    case 2:
        w = os.Stderr
    default:
        // error handling
    }
    count := binary.BigEndian.Uint32(hdr[4:])
    dat := make([]byte, count)
    _, err = r.Read(dat)
    fmt.Fprint(w, string(dat))
}

ただ、自前で実装しなくてもDockerが用意している
github.com/docker/docker/pkg/stdcopy.StdCopy(dstout, dsterr io.Writer, src io.Reader)
を使えば簡単に標準・エラー出力できます。

まとめ

今回のようなストリーム型レスポンスを扱う上で、
HTTPの多重化ストリームの仕組みとエンディアンについて知識が必要になってきます。
これらの知識がなかったので、ちんぷんかんぷんでした。

やっとのこと理解できたので、忘れないうちに記事にしました。
今後DockerのSDKを使って何かつくおうと思っている方にとって、何か役に立てればと思います。


  1. エンディアンはCPUによってデータをメモリ上に格納する方法が異なるります。推測になってしまうが、おそらくDocker側はビッグエンディアンに統一させているようです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のSlice

Go言語のSliceもよく使うタイプです。
Arrayと似ているのですが、Array型ではないです。
中ではArrayを使っています。可変長の配列Arrayの場合はSlice型でよいでしょう

Slice定義方法

例:

var slice1 []int
fmt.Println(slice1)

出力:

[]

配列から抽出してSlice生成

配列変数名[開始index:終了index]
開始indexと終了indexは省略可能です。
また終了indexの要素は含まないです。

開始indexがない場合は、[0:終了index]と同じで、最初から終了indexまで
終了indexがない場合は、[開始index:配列長さ]と同じで、開始indexから最後まで

例1:

var months = [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var slice1 = months[4:10]
fmt.Println(slice1)

出力:

[5 6 7 8 9 10]

例2:

var months = [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var slice1 = months[:]
fmt.Println(slice1)

出力:

[1 2 3 4 5 6 7 8 9 10 11 12]

例3:

var months = [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var slice1 = months[:6]
fmt.Println(slice1)

出力:

[1 2 3 4 5 6]

例4:

var months = [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var slice1 = months[6:]
fmt.Println(slice1)

出力:

[7 8 9 10 11 12]

make関数で生成

var 変数名 = make([]T, length, capacity)

例:

var slice1 = make([]int, 10, 20)
fmt.Println(slice1)

出力:

[0 0 0 0 0 0 0 0 0 0]

Sliceメソッド

長さ:len(変数名)

例:

var slice1 = make([]int, 10, 20)
fmt.Println(len(slice1))

出力:

10

容量:cap(変数名)

例:

var slice1 = make([]int, 10, 20)
fmt.Println(cap(slice1))

出力:

20

Reslice

例:

var slice1 = make([]int, 10, 20)
var slice2 = slice1[8:10]
fmt.Println(slice2)
fmt.Println("len:", len(slice2))
fmt.Println("cap:", cap(slice2))

出力:

[0 0]
len: 2
cap: 12

特に容量は、元Sliceの最後までの数です。
slice2は、8~20までの数は12です。

Append:要素追加

例1:

var slice1 = make([]int, 10, 20)
slice1 = append(slice1, 11, 22, 33)
fmt.Println(slice1)
fmt.Println("len:", len(slice1))
fmt.Println("cap:", cap(slice1))
fmt.Printf("address:%p\n", slice1)

出力:

[0 0 0 0 0 0 0 0 0 0 11 22 33]
len: 13
cap: 20
address:0xc000092000

容量は変わっていないです。

例2:

var slice1 = make([]int, 10, 20)
slice1 = append(slice1, 11, 22, 33, 44, 55, 66, 77, 88, 99, 100, 111)
fmt.Println(slice1)
fmt.Println("len:", len(slice1))
fmt.Println("cap:", cap(slice1))
fmt.Printf("address:%p\n", slice1)

出力:

[0 0 0 0 0 0 0 0 0 0 11 22 33 44 55 66 77 88 99 100 111]
len: 21
cap: 40
address:0xc00007e000

容量は変わると、メモリアドレスも変わります。

copy:copy(コピー先, コピー元)

copy(toSlice, fromSlice)

例:

var slice1 = []int{1, 2, 3, 4, 5}
var slice2 = []int{6, 7, 8}

copy(slice1, slice2)
fmt.Println("slice1:", slice1)
fmt.Println("slice2:", slice2)

var slice3 = []int{1, 2, 3, 4, 5}
var slice4 = []int{6, 7, 8}
copy(slice4, slice3)
fmt.Println("slice3:", slice3)
fmt.Println("slice4:", slice4)

出力:

slice1: [6 7 8 4 5]
slice2: [6 7 8]
slice3: [1 2 3 4 5]
slice4: [1 2 3]

copy関数はsliceの長さは変更しないです。コピー先に長さまでコピーします。

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のArray

Goの中でArrayは、型と長さ合わせてArray型になります。
違う長さの場合は == または != で比較するとエラーになります。

GoのArray型の書き方

基本書き方

var 変数名 = [n]型 {値1,...,値n}
例:

var months = [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

出力:

[1 2 3 4 5 6 7 8 9 10 11 12]

長さ省略

「...」を利用して長さは省略可能です。
例:

var months = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

出力:

[1 2 3 4 5 6 7 8 9 10 11 12]

値指定なし

例:

var arr = [12]int{}

出力:

[0 0 0 0 0 0 0 0 0 0 0 0]

ちなみに、省略すると、長さは0のArrayになります。
例:

var arr = [...]int{}

出力:

[]

一部のみ値指定

例:

var arr = [12]int{8: 9, 11: 12}

出力:

[0 0 0 0 0 0 0 0 9 0 0 12]

indexは0からです。8:9の場合は、9個目の値は9で設定されます。

要素値のアクセス

変数[index]
例:

var arr = [12]int{8: 9, 11: 12}
fmt.Println(arr[8])

出力:

9

要素値の変更

変数[index] = 新しい値
例:

var arr = [12]int{8: 9, 11: 12}
arr[8] = 999
fmt.Println(arr[8])

出力:

999

Arrayのメソッド

長さ:len(変数名)

例:

var array = [12]int{8: 9, 11: 12}  
fmt.Println(len(array))

出力:

12

容量:cap(変数名)

var array = [12]int{8: 9, 11: 12}  
fmt.Println(cap(array))

出力:

12

Array比較

Arrayは同じ型、同じ長さの場合だけ比較できます。

「==」で等しい判断

例:

var arr1 = [12]int{8: 9, 11: 12}
var arr2 = [12]int{8: 9, 11: 12}
fmt.Println(arr1 == arr2)

出力:

true

「!=」で等しくない判断

例:

var arr1 = [12]int{8: 9, 11: 12}
var arr2 = [12]int{8: 90, 11: 120}
fmt.Println(arr1 != arr2)

出力:

true

長さが違う場合は比較できない

例:

var arr1 = [12]int{8: 9, 11: 12}
var arr2 = [24]int{8: 90, 11: 120}
fmt.Println(arr1 == arr2)

出力:

invalid operation: arr1 == arr2 (mismatched types [12]int and [24]int)

二次元Array

例:

var data = [3][2]int{{1, 2}, {11, 22}, {111, 222}}

出力:

[[1 2] [11 22] [111 222]]

ちなみに、一つ目の長さは省略可能です。
例:

var data = [...][2]int{{1, 2}, {11, 22}, {111, 222}}

出力:

[[1 2] [11 22] [111 222]]

操作は一元Arrayと同じ
例:データアクセス

var data = [...][2]int{{1, 2}, {11, 22}, {111, 222}}
fmt.Println(data[1][1])

出力:

22

ポインター定義

Arrayが値引用のため、関数の中で加工したい場合は、ポインターで定義
例1:

arr := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var p *[12]int = &arr
fmt.Println(p)

出力:

&[1 2 3 4 5 6 7 8 9 10 11 12]

例2:newで定義

p := new([10]int)
p[9] = 10
fmt.Println(p)

出力:

&[0 0 0 0 0 0 0 0 0 10]

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golangで簡単なweb backendを書いた

経緯

僕、普段はPythonとDjangoを使って 引きこもりしつつ飛び上がり自殺を考えながら 独自にwebアプリを書いているのですが、最近ではRailsでもSinatraでもFlaskでもDjangoでもPyramidでもなく、Goとそのフレームワークを使ってバックエンドコードを書いている(ように見える)ウェブアプリが見られます。

Goは並列処理がお得意な言語ということでよく知られていますが、今回、実際にGoでコードを書いてみて分かった事をまとめてみました。

書いたコード

使ったフレームワーク

分かったこと

Go言語そのものについて

Go言語それ自体は並列処理が言語機能の一つとして実装されているという点を除けば、非同期処理を得意とする他の言語(NodeJSなど)とほぼ同じような感じ?の印象を受けました。しかし、コンパイル型かつ並列・非同期処理が得意な言語という事で、Pythonなどと比べてウェブアプリを実装する時に飛躍的なスループットの向上が見込めるように思えます。(小並感

フレームワークについて

  • 標準ライブラリ(net/httpなど)はやっぱりWeb開発用途を意識されているようです。でも、DjangoのMiddleware的な概念はchiなど他のライブラリを使わないと面倒でした(小並感
  • GinはRESTful APIを書く時には使えると思いました。が、Restful APIを書くんだったらOpenAPI Generatorを使うよね、うん。
  • GormはPythonで言うところのSQLAlchemy、Djangoで言うところのModel的な存在です。そして、ORMとしてのGormは至極普通でした。が、AutoMigration機能がDjangoのそれとは劣ります。(GormのAutoMigrationとDjangoのMigrationの比較参照)
  • GQLGenでResolverを書く時にResponseWriterを参照できない。つまり、Backendとして認証機能を実装する場合はフロントエンド側でひと手間掛ける必要がある?(例: セッションのJWTをフロントエンドへペイロードとして渡してフロントエンド側でこいつをCookieなりヘッダなりにぶちこむ)
  • 認証機能はモノリシックに作るのではなく、別にライブラリとして提供したほうが良さそう。

GormのAutoMigrationとDjangoのMigrationの比較

機能 Gorm Django
Tableの自動追加
Tableの自動削除
Tableの自動リネーム
カラム自動追加
カラム自動削除
カラム自動変更
マイグレーションのバージョニング ❌(但しサードパーティー製ライブラリで対処可能?)
マイグレーションの自動生成 ❌(その概念がない?)
マイグレーションの手動記述

...GormのAutoMigrationは実用に値しない?ORMの機能だけ頂いてMigration Manager的な代物を書いたほうが良さそう。

まとめ

GolangはAPIの開発には良さげだと思われますが、現状ではWebアプリのバックエンドとしての利用は難しい?ように思えました。また、Djangoではモデルデータの管理の為の画面(django.contrib.admin)があり、Golangも同様の機能が提供されている(QOR)ようですが筆者がこれを利用しようとしたところ、GQLGenの仕様?によってうまく使えませんでした。

と、いうわけで、Golangはセッション、SQLのスキーママイグレーション、モデル管理を解決できればメインで使える言語に出来るんじゃないかと思いました まる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語Go 第3章 メモ

「基本データ型」

書籍:プログラミング言語Go
第3章 「基本データ型」 の要点と思われる箇所を自分のメモ用にまとめました。

3.1 整数

  • int, uintは32か64bitだが、そのサイズがどちらであるか想定した実装をしてはいけない。コンパイラで変わりうるから。
  • uintptr は幅の規定がない。ポインタの値のすべてのビットを保持するのに十分な幅は持つ。
  • 演算子は結びつきの優先度が5段階ある。同じ優先度の演算子が並ぶ場合、左に結び付く。わかりやすさのため () でくくること推奨。
  • 余剰演算子 % は整数のみ利用可。負の余剰の振る舞いは言語で異なり、Goでは剰余の符号は被除数 (%の左の数値) の符号と同じになる。
  • オーバーフロー時は方に収まらない上位ビットが破棄される。
  • 符号付数値の右へのビットシフトは空いたビットに符号ビットをコピーして埋める。整数をビットパターンとして扱う場合、符号なし算術を使うことが重要。
  • sliceのlenは明らかに正の整数だが、int型。for文の初期化節でlen使った際にuintで返されると、iがuintになり、思わぬ動作になりうる。
  • 上記などを理由に符号なし数値は単なる負ではない数量に対しては普通は使われない。
  • 符号なし数値はビットセットの実装、バイナリファイル形式の解析、ハッシュや暗号化のビット演算子、普通と異なる算術演算子が必要な場合用いられる。
  • T(x)により大きい整数から小さい整数型に変換したり、整数を浮動小数点型に(逆も)変換する場合、値が変更されたり精度を失いうる。★
  • Printfのverbで%の後に[1]とすると、第一オペランドが利用される。
  fmt.Printf("%d, %[1]x", x) // %d, %[1]x ともに第一オペランド x が利用される。

3.2 浮動小数点数

  • 浮動小数点数の限界値は mathパッケージ に定義されている。
  • float32は6桁の、float64は15桁の10進の精度を有す。float32は誤差が大きいのでfloat64を使うのが好ましい。★
  • mathパッケージには正の無限大、負の無限大、NaN(not a number:0/0などの数学的に疑わしい演算の結果) の定義などがある。
  • math,NaN() 同士の比較演算結果は != を除いて常にfalseになるので注意。
  nan := math.NaN()
  if nan == nan { // false
  if nan < nan {  // false
  if nan > nan {  // false

3.3 複素数

  • complex64, complex128はそれぞれ実部、虚部がfloat32とfloat64の複素数。
  • x := 1 + 2i のように、iをつけると虚数。var x complex128 := complex(1, 2) と同じ。
  • math.complexパッケージで複素数の算術を行える。

3.4 ブーリアン

  • && は || より優先順位が高い。&&はブーリアン乗算、||はブーリアン加算と覚えるとよい。

3.5 文字列

  • len関数は文字列中のバイト数を返す。
  • ASCIIでないコードポイントのUTF-8は2バイト以上必要(1文字が2バイト以上) となるので注意。
  • 文字列の比較演算は辞書順。
  • 文字列は不変なので、文字列のデータを直接変更する構文は許されない。 s[0] = 'L' // コンパイルエラー: s[0] には代入できない。

3.5.1 文字列リテラル

  • Goソースファイルは常にUTF8でエンコードされる。
  • ``で囲うと生文字列リテラルとなり、CR(キャリッジリターン) の削除以外は、``で囲われた文字列がそのまま利用される。エスケープシーケンスも処理されない。改行を含む文字の表示(usage表示とか)に便利。★

3.5.2 Unicode

  • Unicodeは世界中の文字をサポートしていて、各文字にUnicodeコードポイント(Go用語ではルーン) と呼ばれる規格番号を割り当てる。

3.5.3 UTF-8

  • UTF-8はルーン表現のために1から4バイトを利用する。ASCIIは1バイト、その他は最初のバイトの最上位数ビットにその後何バイト続くか示される。
  0xxxxxxx                            :ASCII
  110xxxxx 10xxxxxx                   :2バイト文字
  1110xxxx 10xxxxxx 10xxxxxx          :3バイト文字
  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx :4バイト文字
  • 上記設計により4バイト以上戻ることなく文字の開始を見つけることができる。★
  • Unicodeエスケープを使えばコードポイントの数値で文字を指定できる。
  • コードポイント(ルーン:実際の1文字, 1文字)単位で文字列操作をする場合、unicode/utf8 パッケージのデコーダを使う。
  • Goのrangeが文字列へ適用された場合、UTF-8のデコードを暗黙的に行う。以下でルーン数を簡単に数えられる。★
  s := "Hello, 世界" // 9文字。(len(s) は13)
  n := 0
  for _, _ := range s {
      n++
  }
  fmt.Println(n) // 9
  • UTF-8デコーダが期待しない入力バイトを受け取ると '\uFFFD' (◆の中に白い? がある文字) を生成する。この文字が表示されるとどこかでテキストエンコーディングが不正になっている。
  • UTF-8でエンコードされた文字列に []rune を適用できる。プログラム中でUTF-8文字列を扱うのに便利。

3.5.4 文字列とバイトスライス

  • 文字列は不変のため文字列を少しずつ増やしながら構築することは多数の割り当てとコピーを必要とする。このような場合はbytes.Bufferが有効。
  • strconvパッケージは数値方を文字列変換するなどの関数を提供。
  • unicodeパッケージはルーンを分類するIsDigit, IsLetter, IsUpper, IsLowerなどの関数を提供。各関数は単一のルーンの引数を受け取る。
  • basenameはUnixのユーティリティからヒントを得た機能で、渡されたファイルパスを"/"で要素分割し、拡張子を除いたファイル名を得る。 fmt.Println(basename("a/b/c.go") // "c" fmt.Println(basename("c.d.go") // "c.d" fmt.Println(basename("abc") // "abc"
  • 文字列 s を用いた []bytes(s) 変換はsのバイトのコピーを保持する新たなバイト配列を割り当てる。コピーによりbytes変換したスライスを操作しても元の s には影響しない。
  • bytesはスライスを効率的に操作するためのBuffer型を提供する。BufferにUTF-8のルーンを書き込む際はWiteRuneを使う。ASCIIであればWriteByteで構わない。

3.5.5 文字列と数値の変換

  • strconvを使うほかにfmt.Sprintf()でも数値を文字列変換できる。Sprintfは%b, %xなどのverbを使え、また、数値以外に追加情報を加えられる。
  x := 123
  s := fmt.Sprintf("x=%b", x) // "x=1111011"
  • ParseIntは基数 (2進、10進など) と戻り値として許す最大値のbitサイズを指定できる。

3.6 定数

  • 定数はコンパイル時に評価が行われる式のため実行時の処理を減らす。範囲外インデックスの参照や無限大の浮動小数点演算などのエラーもコンパイル時に検出。★
  • すべての定数の基底型はブーリアン、文字列、数値の基本型。
  • 定数constは () で囲うことができ、関連する定数をグループ化する場合に適する。
  • 定数式の型が明示的に指定されない場合、右辺から推定される。
  • 定数をグループで宣言する場合、最初の定数以外、右辺を省略可能。iota を使うと便利な場面が出てくる。
  const (
      a = 1  // 1
      b      // 1
      c = 2  // 2
      d      // 2
  )

3.6.1 定数生成器 iota

  • iotaは明示的に毎回書くことなく関連した値を生成するために使う。const宣言で使うとゼロから1ずつ増加する。★
  type Weekday int
  const (
      Sunday Weekend = iota
      Monday
      Tuesday
      ...
  • 以下のような使い方もできる。以下はuintの下位5ビットに別々の名前を付けてブーリアンとして解釈している。
  type Flags uint
  const (
      FlagUp Flag = 1 << iota
      FlagBroadcast           // func SetBroadcast(v *Flags) { *v |= FlagBroadcast } 利用例の実装
      FlagLoopback
      FlagPointToPoint
      FlagMulticast
  )
  • 1024の累乗に名前を付けた例。
  const (
      _ = 1 << (10 * iota)
      KiB // 1024    (fmt.Printf("%b", KiB) は 10000000000)
      MiB // 1048576
      ...

3.6.2 型付けなし定数

  • 多くの定数は特定の型に結びついているわけではなく、そのような定数は基本型よりはるかに高い数値精度で表現される。
  • 型への結びつけを遅延させることで、型付けなし定数は高い精度を有し、また、多くの式で変換を必要とせず使える。★
  var x float32 = math.Pi
  var y float64 = math.Pi
  var z complex128 = math.Pi
  • 非対称性に注意。整数の型付けなし定数を望むサイズの型で利用したいときは明示的な変換を使う。
  var i = int8(0)

練習問題解答例

https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter03

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語Go 第2章 メモ

「プログラム構造」

書籍:プログラミング言語Go
第2章 「プログラム構造」 の要点と思われる箇所を自分のメモ用にまとめました。

2.1 名前

  • 名前の最初の文字が大文字か小文字かでその名前がパッケージの境界を越えて見えるか否かが決まる。大文字から始まる場合は公開を意味する。★
  • Goはキャメルケース(単語のはじめを大文字にして単語を繋げる記法。一番最初の文字は小文字だとlowerCamel(<=これが例), 大文字だとUpperCamel(<=これが例))で書くのが慣習らしい。
  • HTMLなどの頭文字の集合は常に大文字か常に小文字で書くのが慣習らしい。HTML, htmlはOKだがHtmlはダメ。

2.2 宣言

  • ソースコードの.goファイルはどのパッケージに属すかを宣言するpackage宣言から始まり、import宣言が続き、型、変数、関数のパッケージレベルの宣言が任意の順で宣言される。
  • 大域的な場所に宣言された小文字から始まるエンティティは、パッケージ内で可視となる。

2.3 変数

  • 変数宣言の = の右辺が省略されるとその変数の型のゼロ値となる。
  • 数値は0、boolはfalse、文字列は ""、インタフェースと参照型はnilがゼロ値。配列や構造体の合成型のゼロ価はすべての要素やフィールドでゼロ値をとる。
  • ゼロ値の利用により、Goには未初期化の変数はない。
  • 複数の変数を宣言し、リストで初期化できる。
  var i, j, k int
  var b, f, s = true, 2.3, "four"
  • パッケージレベルの変数はmainが始まる前に初期化される。ローカル変数は関数の実行中の宣言に出会ったときに初期化される。

2.3.1 省略変数宣言

  • 関数内でローカル変数を宣言して初期化するために利用。大域的なスコープでは利用できない。
  • 複数の変数を同一の省略変数宣言で宣言して初期化できるが、可読性が向上する場合にのみ利用すべき。forループの初期化部分のような短くて自然なグループ化などでのみ利用が推奨。
  • 複数の変数を省略変数宣言で宣言する場合、すべてが新しい変数宣言になるわけではない。err などよく利用する変数の場合、二度目の同一スコープで省略宣言で利用するときは代入のように働く。
  • 省略変数宣言で宣言する変数は少なくとも1つは新しい変数宣言でなければならない。新しい変数宣言がないとコンパイルエラーになる。★

2.3.2 ポインタ

  • 変数に&をつけると変数のポインタに、ポインタに*をつけるとポインタの変数の値にアクセスできる。
  x := 1
  p := &x          // pは*int型で、xを指している
  fmt.Println(*p)  // "1"
  *p = 2           // x = 2と同じ
  fmt.Println(x)   // "2"
  • ポインタのゼロ値はnil。
  • 関数がローカル変数のアドレスを返すのは安全。★
  • ポインタ引数を関数に渡すことで関数が間接的に渡された変数を更新することができる。
  • ポインタは変数のエイリアス。スライス、マップ、チャネルなど他の参照型の値をコピーしたときにもエイリアスが発生する。

2.3.3 new関数

  • newは構文上の利便性であるだけで基本的な概念ではない。あまり使わない。
  • newは事前宣言された関数で、予約語ではないので、関数内で別の概念にnewという名前を使える。

2.3.4 変数の生存期間

  • 変数は到達不可能になるまで生存する。ローカル変数のポインタが返される関数が定義された場合、関数を抜けても返されるローカル変数は到達可能であるため生存し続ける。  このような変数は関数からエスケープしているという。パフォーマンスを最適化する際は、エスケープを意識すると良い。
  • ガベージコレクションは正しいプログラムを作成するために役立つが、メモリについて考える重荷を取り除くものではない。変数の生存期間を意識することは重要。★

2.4 代入

2.4.1 ダブル代入

  • ダブル代入は複数の変数へ一度に代入できる機能。2つの変数の値を交換する場合などに役立つ。
  x, y = y, x
  a[i], a[j] = a[j], a[i]
  • スタイルの問題として式が複雑な場合はダブル代入は避けるべき。★複雑な式への条件演算子の利用などと同じ。
  • 代表的な利用方法としてはos.Openのようにerrorや、okと呼ばれるboolが返されるケース。

2.4.2 代入可能性

  • プログラム内で代入が暗黙的に発生することが数多くある。関数の引数やreturn文、スライスのリテラル式など。
  • 代入可能性の規則はさまざまな型に対応する規則がある。
  • == と != で比較できるかどうかは代入可能性と関係する。第一オペランドまたは第二オペランドがもう片方のオペランドに代入可能である必要がある。

2.5 型宣言

  • type宣言は既存の方と同じ基底型を持つ新たな名前付き方を定義するもので、パッケージレベルで書かれることが一般。
  • type宣言も小文字始まりだとパッケージ内のみに公開、大文字始まりの名前だとパッケージ外に公開される。
  • 同じ基底型のtype宣言でも両者を比較することはできない。型が区別されるので不注意な変数の組み合わせが避けられる。
  • すべての型Tに対して値xをT型に変換するための返還演算 T(x) がある。型変換は返還前後の型が同じ基底型を持つか、両方の型が同じ基底型の変数を指すポインタ型の場合に許される。
  • 名前付き型はその型の値に対して新たな振る舞いを定義することができる。定義された振る舞いはその型のメソッドと呼ばれる。
  • 名前付き型にメソッドを関連付ける場合、func ("型の変数名" "型名") "関数名"("引数") "戻り値型" { で定義する。
  func (c Celsius) String() strint { ... }
  • 名前付き型にStringメソッドを定義すると暗黙で文字列変換する場合にStringメソッドの値を利用してもらえる。★

2.6 パッケージとファイル

  • 個々のパッケージはその宣言に対して別々の名前空間として機能を果たす。
  • パッケージ外に公開するものは識別子の先頭を大文字で始める。
  • 詳細なドックコメントはたいていそれだけを目的とするファイルにかかれ、慣習により doc.go と呼ばれる。

2.6.1 インポート

  • インポートしたパッケージを参照しないとコンパイルエラーとなる。デバッグでfmtをちょっと利用したい時など面倒になる。
  • 上記問題を解決するため golang/org/x/tools/cmd/goimportsツールがあり、必要に応じてimportを挿入・削除してくれる。

2.6.2 パッケージ初期化

  • パッケージレベルの変数の初期化は依存関係が解決され初期化順が決まる。
  • 初期化式による初期化が難しい複雑な変数は、init関数の仕組みを使うのが良い。initの例は以下参照。
  • init関数は呼び出すことも参照することもできないが、プログラムが開始した時点でinit関数は宣言順に自動的に実行される。
  var pc [256]byte
  func init() {
      for i := range pc { // for i, _ := range pc { と同じ。
          pc[i] = pc[1/2] + byte(i&1)
      }
  }

2.7 スコープ

  • 変数宣言は同じパッケージ内のどのファイルからも参照できるが、importはそのファイル内のみで有効。
  • 個々の宣言が異なるレキシカルスコープにあれば同じ名前を複数宣言できる。が、コードの読み手を混乱させるのであまり利用すべきでない。
  • レキシカルブロックは必ずしも明示的に {} で囲われているわけではない。for文の初期化節など。
  • 異なるレキシカルスコープの変数名を := で宣言してしまうと、別の変数扱いになるので注意。★
  var cwd string
  func init() {
      cwd, err := os.Getwd() // コンパイルエラー: 未使用: cwd
      if err != nil {
          log.Fatalf("os.Getwd failed: %v", err)
      }
  }
  • 上記例は init() 内でcwdを利用していると、コンパイルエラーにならずに思わぬバグになりうる。init() 内ではなるべく := を使わないのが良さそう。
  var cwd string
  func init() {
      var err error
      cwd, err = os.Getwd() // コンパイルエラー: 未使用: cwd
      if err != nil {
          log.Fatalf("os.Getwd failed: %v", err)
      }
  }

練習問題解答例

https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter02

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語Go 第1章 メモ

「チュートリアル」

書籍:プログラミング言語Go
第1章 「チュートリアル」 の要点と思われる箇所を自分のメモ用にまとめました。

準備

https://golang.org/dl/
上記からインストーラをダウンロードしてGoを使えるようにする。

https://play.golang.org/
簡単なコードを実行したい場合は上記が便利。

1.1 ハロー、ワールド

  • Goはコンパイル言語で、コンパイルはbuildサブコマンドを使う。
 $ go build xxx.go
  • goコマンドにはサブコマンドがいくつもあり代表例がrun。go runで1つ以上の.goファイルを指定するとコンパイルしたものの実行結果を確認できる。
 $ go run xxx.go
  • GoはUnicodeを扱い、世界中のすべての言語テキストを処理できる。
  • Goにはパッケージという概念があり、必要な機能はパッケージをimportして利用する。
  • mainパッケージ内のmain関数からプログラムが実行される。(C++のmain関数と同じ。)
  • Goはコードの書式に関して強い立場をとっている。スタイルが厳密にあり、gofmtをつかうと標準書式に成型してくれる。(ので、gofmtを積極的に使おう。)★
    • 基本的に改行は他言語のセミコロン相当に解釈される。
    • 関数の開き括弧 { は関数宣言の終わりと同じ行にないといけない。
    • ...

1.2 コマンドライン引数

  • os.Argsでプログラム実行の引数が把握できる。
  • スライスとは、動的に大きさが決まる配列要素のシーケンスのようなもの。
    • 要素には[i]の添え字でアクセスする。添え字の数字は0始まり。 Ex) os.Args[1]
    • 要素数はlen(s)で得られる。 Ex) len(os.Args)
    • スライスに添え字[m:n]を適用すると、m番目からn-1番目の要素を参照するスライスになる。m, nは省略可能で、mは省略すると0が, nは省略すると最後の要素まで指定されたことになる。
  • コメントは//で始める。/* */ も使える。
  • var宣言で初期化をしないとその型のゼロ値となる。(整数値は0, 文字列は""など。)
  • :=は省略変数宣言。初期化の値に基づき適切な型を変数に割り当てる。大域的スコープでは利用できない。
  • インクリメント演算子は後置のみ。かつ、インクリメントを利用した行は文であり、式として利用できない (j = i++ などとできない)。★前置・後置の戻り値の違いを理解していないためにおこるバグが発生しない。
  • Goには値を2つ以上返す関数がある。★値もエラーも返してほしいケースなどで活躍!
  for i, arg := range os.Args[1:] {
  • _ はブランク識別子と呼ばれ、変数名が必要だけどその変数を利用しないケースで利用する。
  for _, arg := range os.Args[1:]  { // rangeの第一戻り値はindexだが、indexを利用しない場合 _ を使う。
  • 以下は同じ変数宣言。初期値が重要であれば1番目の表現を、初期値が問題でないなら2番目の表現を使うのが良い。
  s := ""
  var s string
  var s = ""
  var s string = ""

1.3 重複した行を見つける

  • mapのkeyには==で比較できる値が入る。valueは何でもOK。
  • valueがないindexに添え字でアクセスするとvalueがゼロ値で評価される。
  • mapの繰り返しの順序は規定されておらず、むしろ実際には実行時にランダムに異なる。★暗黙のバグが防げる。すごい。
  • fmt.Printfはverbと呼ぶ変換を持つ(Cのprintfみたいに%dとか%sを使う。)。慣習的にfで終わるフォーマット関数はフォーマット規則を利用する。
  • goにはnilという概念があり、nilはエラーなしを意味することがある。
  • 関数とほかのパッケージレベルのエンティティはどのような順序で宣言してもOK。関数呼び出しが、その関数定義の前にあってもよい。
  • mapが関数へ渡される場合、その関数はmapの参照のコピーを受け取る。(呼び出し元と呼び出され側は同じmapを扱う。関数内でmapを操作したら、呼び出し元のmapもその操作が反映される。)

1.4 GIFアニメーション

  • importしたパッケージはコード中では最後の要素の名前でそのパッケージを参照する。
    • Ex) image/gif をimportした場合、gif.GIF{…
  • constの値は、数値、文字列、ブーリアンでなければならない。
  • {}を使うとコンポジットリテラルとしてコンポジット型のインスタンスを生成できる。
  []color.Color{color.White, color.Black}
  Anim := gif.GIF{LoopCount:nframes}
  • 大域的な場所で省略変数宣言する( := を使う)と以下のエラーになる。var宣言して=して対応。
syntax error: non-declaration statement outside function body

1.5 URLからの取得

  • Goはインターネットを通した情報の送受信や低レベルネットワーク通信のためにnetパッケージが存在。
  • os.Exit(1) はプロセスをステータスコード1で終了させる。

1.6 URLからの並行な取得

  • ゴルーチンは関数の並行した実行。チャネルはゴルーチンが特定の型の値を他のゴルーチンへ渡すことを可能にする通信機構。
  • 1つのゴルーチンがチャネルに対して送信または受信を試みると、そのゴルーチンは他のゴルーチンが対応する受信操作あるいは送信操作を試みるまで待たされ、値が転送された時点で2つのゴルーチンは処理を先に進める。

1.7 ウェブサーバ

  • Goライブラリを使えばウェブサーバを容易に作成できる。
  • サーバサイドの実装ではURLにハンドラ関数を結びつける。
  http.HandleFunc("/", handler) // handlerはハンドラ関数。ハンドラ関数にhttpリクエストの処理を記述する。
  • ハンドラはリクエストごとに別のゴルーチンで実行され、複数のリクエストを同時に処理できる。それぞれが並列処理になるのでスレッドセーフにする必要がある
  • Goでは関数リテラルが使える関数リテラルは使われる時点で定義される無名関数。以下のHandleFuncの第二引数が関数リテラル。
  http.HandleFunc("/", func(w, http.ResponseWriter, r *http.Request) {
      lissajous(w)
  })

1.8 残りの項目

  • switch文はcaseが次のcaseに通り抜けることはない (fallthrough文を利用するとこの動作は無効になる。)。この言語仕様により case内での break 忘れのバグが起きにくくなる。★
  • switch文はオペランドを指定しなくても記述できる。以下が例。
  switch {
  case x > 0:
    return 1
  ....
  • goto文も使えるが、自動生成コード以外での利用は推奨されない。コードが読みにくくなるから。
  • Goではポインタを扱え、&で変数のポインタに、*でポインタの値を参照できる。ポインタへの演算はない。
  • go docコマンドでドキュメンテーションを参照できる。
  $ go doc http.ListenAndServe

練習問題解答例

https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter01

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go言語 静的ページの作成

Goの勉強録

go言語を身に付けたいと思って頑張って色々するものの、環境構築とHello Worldをhtmlで表示して「やったー」と思ったまま放置していました。
他に色々忙しいのと、PHPでWebソフト作っていてgoでやる必要性とかを色々考えててモチベが上がらずってのも結構原因でした。
そこで勉強録をつけていけば自分の技術の軌跡がわかるしモチベ向上になるんじゃね?と思って勉強録をつけようと思います。

余談

PHPで開発していた時にMySQLが起動しなくなって初期化からインストールをする際に
/usr/local/Cellar/のなかで,sudo rm -rf *をしてしまったので,brewから導入した環境が全て飛びました。/(^o^)\ナンテコッタイ

そのおかげで、goの環境構築を一からしました。
ちなみにgoの環境構築はGo言語の環境構築 Hello Worldまでに記録してます。

本題

余談はここまでにしておいて,実際にgoで静的ページを生成します。
静的ページとは、誰が閲覧しても内容が変わらないページって事ですね。つまり文書だけしかないページや画像しかないページを思い浮かべて貰えると分かりやすいです。

今回はgoのなかでが他のhtmlファイルを読み込んでそれをローカルホストに表示するってことをやります。

GOPATH~/go/に通している前提で話を進めます。

以下のコマンドで今回のワークスペースを作成

bash
mkdir ~/go/StaticPage/ ~/go/StaticPage/HTML && cd ~/go/StaticPage/
touch main.go ./HTML/index.html

まず、表示用のhtmlを作成

index.html
<!DOCTYPE html>
<html>
  <body>
     <h1>It Works!</h1>
  </body>
</html>

次に、htmlを呼び出すプログラムを書きます。

main.go
package main

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

func main() {
    // このファイルのパスを取得
    dir, _ := os.Getwd()

    // データ型を取得
    fmt.Println(reflect.TypeOf(dir))

    // 第一引数 (/staticpage/) ハンドラの生成
    // 第二引数 (/staticpage/) のリンクの中にhtmlファイルを探す設定
    // 第三引数 // .htmlを配置したディレクトリ (dir+"/HTML/") を読み込む
    http.Handle("/staticpage/", http.StripPrefix("/staticpage/", http.FileServer(http.Dir(dir+"/HTML/"))))

    // ListenAndServe でたちあげれなかった場合のlog
    // nilでhttp.Handleで設定したハンドラーを呼び込み
    log.Fatal(http.ListenAndServe(":8080", nil))
}

ずっとエラーで

./main.go:18:85: cannot use string(dir) (type string) as type http.FileSystem in argument to http.FileServer:
        string does not implement http.FileSystem (missing Open method)

って言われていてデータ型のエラーだと思ってreflectをimportしたTypeOfで引数のデータ型が知れることがわかった。
今後デバッグの際に使うかも
結局、http.Dir()が第三引数の呼び出す関数の引数に書き忘れていただけだった/(^o^)\ヨクアル!

ここまできたらあとは実行するだけ

bash:bash
go run main.go

ブラウザでhttp://localhost:8080/staticpage/を検索するとIt Works!と表示される。http.HandlehttpHandlerの違いがイマイチなので今度ぶち当たったらそれも記事としてあげます。

参考

https://liginc.co.jp/333868
http://y0m0r.hateblo.jp/entry/20140413/1397397593

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む