20200121のGoに関する記事は6件です。

Go for文の"_"の使い方

golangのfor文を勉強していて

"_"の意味を理解したのでまとめておきます。

結論から言いますと

インデックス番号を表示する必要がない場合に
"_"を使います。

main.go
package main

import "fmt"

func main(){

    //スライス作成
    l := []string{"jave", "python", "go"}

    //インデックス番号とスライスの要素を表示
    for i:=0; i < len(l); i++{
        fmt.Println(i, l[i])
    }
    //スライスの要素だけを表示
    for  _, v := range l{
        fmt.Println(v)
    }

    //"_"を使わずに要素を表示させようとすると
    for  v := range l{
        fmt.Println(v)
    }
    //インデックス番号が表示される
    //"_"でインデックス番号を埋める必要がある


    //map作成
    m := map[string]int{"apple": 100, "banana":200}

    //mapのキーと値を表示   
    for k, v := range m{
        fmt.Println(k, v)
    }

    //mapのキーを表示
    for k := range m{
        fmt.Println(k)
    }

    //mapの値を表示
    for _, v := range m{
        fmt.Println(v)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golangで標準入力

基本的な標準入力の方法

Go言語で標準入力を行う方法をまとめておきます。

paizaのスキルチェックに標準入力は必須です。

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main(){
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        ucl := scanner.Text()
        fmt.Println(ucl)
    }   
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

k8sとAPI Gateway(Ambassador)を用いたコンテナ管理

こんにちは。kwashiです。SPAによるフロントエンドと、APIサーバの組み合わせで構築したシステムを実装したので紹介します。前職で、モノリシックなシステムに、大規模な機能を追加して苦労したため機能や構成の分離を意識して実装しました。もし、Ambassadorやk8sのマニフェストを参考にしたい場合は、記事の後半に書いています。

※ 実装したシステムは、何らかのサービスを提供する仕様ではなく、技術的な検証・ポートフォリオを目的として作成しました。

SPAは、サーバーサイド側でのHTMLレンダリングを行わず、サーバーサイド側の役割をAPIサーバーに徹底させるために用いています。各APIはサービスごとにコンテナ化することで分離し、コンテナ管理ツールで各サービスを管理することで、スケーリングやデプロイが容易にできるようにしています。また、各APIへのトラフィックをルーティングするためAPI Gatewayを使用しています。

上記のような構成にした場合、認証機能をAPIサーバーとフロントエンド両方でセキュアに実装する必要があり面倒なので、Firebase Authenticationという認証基盤を用いて認証させます。

使用技術の実装に関しては、他の記事との重複も多いため、参考も含め別のQiita記事を紹介することとし、本記事では、システムの概要、k8sでの管理やAPI Gateway Ambassadorの設定ファイルに関して、記述していきます。

※ 今回の実装では、データベースにアクセスする部分を隔離して実装し、DBに関する実装は省いています。

git: (k-washi/example-k8s-ambassador)[https://github.com/k-washi/example-k8s-ambassador]

システム概要

overview2.png

図のように、クライアント側は、Vueを用いており、Nginxから発行されます。APIサーバーは、k8sで管理しており、認証が必要ないREST-API1とJWTの検証が必要なREST-API2、そして、JWTの検証を行うJWT-AutoOのDockerImageを実行しています。
また、ルーティング制御などを行うAPI GatewayとしてAmbassadorを用いています。

クライアント側では、Firebaseと連携し、ログイン、ユーサー登録、ユーザ情報の取得、更新を可能としています。
また、REST-API1では、簡易なJSONによるGET, POSTの機能を提供しています。
REST-API2は、ユーザー情報に基づいたサービス提供の例として、JWT依存の認証、認可を伴うユーザー情報を提供しています。REST-API2の認証、認可は、gRPCを用いてJWTをJWT-AuthOに送信し、JWT-AuthOでFirebaseと連携してJWTの検証を行っています。
また、ユーザidに紐付けてJWTに証明部分を保存しておき、認証タイミングで新たに発行されたJWTであるかどうか確認も行っています。

※基本的にJWTを用いる場合は、重要なデータは、管理すべきではないという意見もあります。そこで、クライアント側がFirebaseと連携し認証することで取得した新たなJWTを添えて、サーバーにアクセスした場合に、JWT-AuthOでは認証の状態であると認識し、最新のJWTであるかどうかに関わらず、JWTが送られてきた場合は認可の状態であると認識するような仕組みにしています。
つまり、重要なデータにアクセスするときは、常にクライアント側がFirebaseと連携した認証を行うということになります。
(このJWT周りに関しては議論すべきであると思います、、、)

API Gatewayは以下のルーティングを制御しています。また、各APIに対するDocker ImageとGitも以下の通りです。
※API Gatewayのportは30000に設定している。

- path: "/:80"
 - image: kwashizaki/example-vue-cli
 - git: https://github.com/k-washi/example-vue-cli.git
 - msg:vueにより構築したフロントエンド


- paths: ["/api/ex-golang/rest-api/", "/api/ex-golang/health/"]
 - image: kwashizaki/example-golang-rest-api
 - git: https://github.com/k-washi/example-golang-rest-api.git
 - msg: REST-API1が提供するAPI(GET, POSTで文を提供、保存, & healthでstatus 200を返答)

- paths: ["/api/ex-jwt/jwt/ex-jwt-auth", "/api/ex-jwt/auth/ex-authentication"]
 - images: kwashizaki/example-golang-jwt-auth-client:v1.0.0
 - git: https://github.com/k-washi/example-golang-jwt-auth/tree/master/testApp
 - msg: REST-API2が提供するJWT-AuthOによるJWT検証を伴うユーザー情報の提供

- paths: ["/ex-jwt-sr/"] #gRPC
 - image: kwashizaki/example-golang-jwt-auth-server:v1.0.0
 - git: https://github.com/k-washi/example-golang-jwt-auth
 - msg: JWT-AuthOにおけるJWT検証( gRPCサーバー)

使用技術の概要

次に、使用した技術を紹介します。私が以前書いた記事にリンクを飛ばしているので、参考にしてみてください。
個人的には、golangの記事が気に入っているのでぜひ!!

Vue.js

現在人気のJavaScriptフレームワークです。個人的に、Reactの非同期処理に使用するRedux-sagaの学習コストが高いと感じ、また、使用感もVue.jsの方が良かったのでVue.jsを選択しました。
フロントエンドにおいてSPAを作成するために使用。ルーティングにvue-router, ユーザー名など全体で使用する状態の管理にVuex, UIコンポーネントフレームワークとしてVuetifyを用いました。

# vue --version
3.10.0

基本的には、以下の私の過去記事を拡張した実装にしている。(Git: https://github.com/k-washi/example-vue-cli.git )
Qiita: Vue.js (Vuex, vue-router, vuetify) とFirebaseで始めるユーザー管理

Golang

Pythonを使い続けていたのですが、静的型付け言語も使用してみたかったので最近流行っているGolangをバックエンドの言語として選択。
並列処理が書きやすく、標準パッケージのサポートが強力。
本システムでは、各サービスをコンテナ化しており、コンテナを軽量化できるという利点がある。

例えば、今回Golangで作成したサービスは、以下のように20MB程度でDockerコンテナ化できる。

REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
kwashizaki/example-golang-jwt-auth-server   v1.0.0              4cf9b4595b01        7 hours ago         23.9MB
kwashizaki/example-golang-jwt-auth-client   v1.0.0              baa4bce6c5fe        44 hours ago        24.5MB
kwashizaki/example-golang-rest-api          v1.0.0              8d92d819d8ad        8 days ago          22.6MB

また、Webフレームワークとして、軽量かつシンプルなginを使用した。

本システムのAPIは、基本的には、私が以前書いたはじめてのGolang Webアプリケーション ~ テスト, Dockerコンテナ化までと同様の構成で実装している。

Firebase

Firebaseは、Googleが提供している、MBaas(Mobile Backend as a service)の一つです。機能として、データーベースや認証があり、本システムでは、認証機能であるFirebase Authenticationを使用した。趣味程度の範囲では無料で使用できます。

Firebase Authenticationは、フロントエンドにおいて認証の結果、JWT((Json Web Token))を送り返す。
サーバー側は、フロントエンドから送られてきたJWTを用いてFirebaseに検証してもらい、有効なJWTかどうか判断することが可能である。
つまり、サーバー側で、ログイン済のユーザーかどうか、JWTを用いて検証できる。

※ サーバー側では、検証に必要なパラメータを定義したex-firebase-auth-firebase-adminsdk-xxxx.jsonファイルが必要である。
firebase SDKにて提供されている。

また、Golangによる実装は、「Vue.js + Go言語 + Firebase 」で始める! Frontend & Backend API 両方で認証するセキュアなSPA開発ハンズオン!を参考にした。

JWT

以下のフォーマットに従って構成された文字列である。

{base64エンコードしたhead1er}.{base64エンコードしたclaims}.{署名}

発行者(今回は、Firebase)が鍵を使用して、JSONに署名することでトークンとして扱うことができる。また、鍵を使用して検証することで改善を検知できる。
claims部には、ユーザー名などの任意の情報を含めることができる。

JWTを分解してclaims部分を抽出、そして、Firebaseにおける情報をデコードするライブラリを、k-washi/jwt-decodeに作成した。

Qiita: Golang によるfirebase AuthenticationにおけるJWT解析

nginx

OSとアプリケーションソフトウェアとアプリケーションとの仲立ちをするミドルウェアの一つで、HTTPリクエストなどを送ったときに、レスポンスを返すWebサーバーソフトウェア。リバースプロキシやロードバランサ機能があり、Apacheと比較して、早くて高付加に強い。

SPAを配布するためのWebサーバーとして用いた。

Qiita: Vue.jsプロジェクトにおけるnginxの設定とDockerによるコンテナ化の例

gRPC

gRPCは、RPC(Remoto Procedure Call)を実現するためにGoogleが開発したプロトコルの一つです。Protocol Buffersを使ってデータをシリアライズし、高速な通信を実現できる点が特徴です。gRPCに関しては、gRPCって何?が参考になりました。

本システムでは、バックエンドにおいてサービス間の通信に使用しました。

Qiita: Golangで始めるgRPC

Docker

ホストマシンのカーネルを利用しプロセスやユーザなどを隔離することで、あたかも別のマシンが動いているかのように動かします。そのため、軽量で高速に仮想環境を起動、停止などが可能です。

私が以前書いたはじめてのGolang Webアプリケーション ~ テスト, Dockerコンテナ化までという記事にGolangのDockerコンテナ化の方法を載せています。

今回、システムに用いたDockerImageは以下の通りです。

REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
kwashizaki/example-vue-cli                  v1.0.0              7d1f0394bec3        2 hours ago         25.3MB
kwashizaki/example-golang-jwt-auth-server   v1.0.0              4cf9b4595b01        7 hours ago         23.9MB
kwashizaki/example-golang-jwt-auth-client   v1.0.0              baa4bce6c5fe        44 hours ago        24.5MB
kwashizaki/example-golang-rest-api          v1.0.0              8d92d819d8ad        8 days ago          22.6MB
quay.io/datawire/ambassador                 0.83.0              d8caf63d933c        3 weeks ago         691MB

Kubernetes(k8s)

自動デプロイ、スケーリング、アプリ・コンテナの運用自動化のために設計されたオープンソースのプラットフォームです。
本記事では、本システムのk8s設定方法に関して、説明していきます。

Ambassador

公式より

Ambassadorはマイクロサービス用のAPIGatewayです。

機能

  • AWS APIGatewayのようなAPIGatewayのホスティング機能
  • Kongのような伝統的なAPIGateway機能
  • Nginxや, Envoy, k8sのIngressのようなProxy機能

具体的な機能

  • 細かなルーティング制御、正規表現ベースのルーティング、ホストルーティングなどが可能
  • 認証
  • gRPC, HTTP/2をサポート
  • カナリアリリース
  • シャドートラッキング機能
  • 特定サービスへのL7トラフィックの透過的な監視

運用者(Ops)の観点

  • ルーティングとスケーリングをEnvoyとKubernetesに依存しているので、展開と操作が簡単
  • TLSTerminationとリダイレクトを広範囲に運用可能
  • トラブルシューティング時に統合的に診断する事が可能
  • 複数の異なるバージョンのAmbassadorを運用出来て、簡単にテスト、更新する事が可能
  • Istioと連携してサービスメッシュ化が可能

内部実装

  • k8sを最大限に利用し、信頼性(reliability), 可用性(availability), スケーラビリティ(scalability)を担保
  • 状態管理の為に、データベースのようなストレージを必要とせず、Kubernetes内に全ての状態管理を維持
  • スケール時は、k8sのReplicasetでレプリカ数を変更するか、horizontal pod autoscalerを利用する事で簡単にスケール事が可能
  • EnvoyProxyを使用して、全てのトラフィックのルーティングとプロキシーを実行

プログラム等のVersion

# go version
go version go1.12.7 darwin/amd64

# docker -v
Docker version 19.03.2, build 6a30dfc

# kubectl version
>lient Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.6", GitCommit:"96fac5cd13a5dc064f7d9f4f23030a6aeface6cc", GitTreeState:"clean", BuildDate:"2019-08-19T11:13:49Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.6", GitCommit:"96fac5cd13a5dc064f7d9f4f23030a6aeface6cc", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:16Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}

# vue --version
3.10.0

k8s マニフェスト

本章では、以下のようなpod, service, deploymentsを立ち上げる。
サービスを見て分かるように、localhost:30000としてExternal ipが設定されている。
一方で、

kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
ambassador-55d75bc95b-29ckv   1/1     Running   0          3m59s
ambassador-55d75bc95b-2lxzv   1/1     Running   0          3m27s
ambassador-55d75bc95b-9vjnz   1/1     Running   0          2m18s
ex-go-5c5747dbdb-ddkpx        1/1     Running   1          5d21h
ex-jwt-cl-576556588d-7sdgc    1/1     Running   1          5d21h
ex-jwt-sr-78bdf9f4d4-6dmft    1/1     Running   1          5d21h

kubectl get svc
NAME               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE
ambassador         LoadBalancer   10.97.238.197    localhost     30000:30685/TCP   5d21h
ambassador-admin   NodePort       10.103.162.214   <none>        8877:31915/TCP    5d21h
ex-go              ClusterIP      10.101.114.104   <none>        8080/TCP          5d21h
ex-jwt-cl          ClusterIP      10.106.203.56    <none>        8080/TCP          5d21h
ex-jwt-sr          ClusterIP      10.99.209.88     <none>        8080/TCP          5d21h
kubernetes         ClusterIP      10.96.0.1        <none>        443/TCP           92d

kubectl get deployment
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
ambassador   3/3     3            3           5d22h
ex-go        1/1     1            1           5d22h
ex-jwt-cl    1/1     1            1           5d22h
ex-jwt-sr    1/1     1            1           5d22h

Secret機能

k8sのSecret機能を用いて、Firebaseの設定ファイルなど漏らしたくない秘密情報を設定する。

env/env-secret.txt
google_app_creds=/tmp/ex-firebase-auth-firebase-adminsdk-xxxxxxx.json

のファイルを作成し、

kubectl create secret generic --save-config firebase-secret --from-env-file ./env/env-secret.txt


のコマンドを用いて以下の、ファイルの設定を読み込む。
確認は、

kubectl get secret
#NAME                     TYPE                                  DATA   AGE
#firebase-secret          Opaque                                1      5d21h

ここで設定したファイルのパスへ、DockerImageをデプロイするときに、Firebase SDKの設定ファイルをコピーする。

API Gateway Ambassadorの設定

k8sはRBACという、各種リソースへのアクセス権限を管理する仕組みです。それが有効であるとして、公式で提供されている、manifestをインストールします。

kubectl apply -f https://getambassador.io/yaml/ambassador/ambassador-rbac.yaml

一応Git:example-k8s-ambassador/ambassador/ambassador-rbac.yamlに、実際に使用したプログラムをおいています。

基本的には、
1. ambassadorというサービスアカウントとサービス等に権限を与えるClusterRoleをClusterRoleBindingで紐付け
2. ambassadorコンテナを使用するport番号(http:80, admin:8877)で作成し、レプリカ数3でDeploymentを作成。
3. NodoPortとして、port(admin:8877)を紐付けてサービスを作成
を行っている。

NodePortととしてServiceを作成したので、以下のようにLoodBalancerを設定している。
httpのportは30000に上書きし、コンテナで受け付けるポートを8080に設定している。

kubectl apply -f ambassador/ambassador-service.yaml 

ambassador/ambassador-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: ambassador
spec:
  type: LoadBalancer
  #externalTrafficPolicy: Local
  ports:
   - name: http
     port: 30000
     targetPort: 8080
  selector:
    service: ambassador

Config Map

k8s内で使用する変数を設定している。
このあと、各サービスをデプロイするので、その際の変数として使用する。

kubectl apply -f example-golang-vue/example-jwt-config.yaml
example-golang-vue/example-jwt-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ex-jwt-map
data:
  origin.host: localhost
  #数字は, ""で囲む
  origin.port: "80"
  ambassador.host: ex-jwt-sr
  ambassador.port: "8080"
  jwtserver.host: localhost
  jwtserver.port: "50051"

コンテナのデプロイ

getambassador.io/configにルーティングの設定を行っている。
そこでは、Ambassadorを経由し、http://ex-go:8080としてリクエストするように設定している。
このex-goは、Deploymentに合わせている。

kubectl apply -f example-golang-vue/example-golang.yaml 
example-golang-vue/example-golang.yaml
apiVersion: v1
kind: Service
metadata:
  name: ex-go
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  ex-health-map
      prefix: /api/ex-golang
      service: http://ex-go:8080
spec:
  type: ClusterIP
  ports:
  - name: cl-ip-ex-go
    port: 8080
    targetPort: 8080
  selector:
    app: ex-go

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ex-go
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ex-go
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: ex-go
    spec:
      containers:
      - name: ex-go-ui
        image: kwashizaki/example-golang-rest-api:v1.0.0
        ports:
        - name: ex-go
          containerPort: 8080
        resources:
          limits:
            cpu: "0.1"
            memory: 100Mi

他のサービス、デプロイメントのマニフェストもデプロイする。他のサービスも、上のマニフェストと同様に設定している。
コンテナ内で使用する環境変数として、env配下に設定している。

また、Firebaseの設定に関してはデプロイ時に、Volume配下のパスのファイルをVolumeMountのパスへコピーしている。
これによって、DockerImage内に秘匿すべき設定ファイルを含む必要がなくなる。

gRPCに関しては、ルーティングの制御が特殊で、getambassador.io/confiのserviceを ex-jwt-srとしスキームを書く必要がない。
また、/jwtauth.JwtService/は、gRPCのプロトコルをgolang用に変換したファイルに記載されている。

kubectl apply -f example-golang-vue/example-jwt-server.yaml
kubectl apply -f example-golang-vue/example-jwt-client.yaml
example-golang-vue/example-jwt-client.yaml
apiVersion: v1
kind: Service
metadata:
  name: ex-jwt-cl
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  ex-jwt-cl
      prefix: /api/ex-jwt
      service: http://ex-jwt-cl:8080
spec:
  type: ClusterIP
  ports:
  - name: cl-ip-ex-jwt
    port: 8080
    targetPort: 8080
  selector:
    app: ex-jwt-cl

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ex-jwt-cl
spec:
  replicas: 1
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: ex-jwt-cl
    spec:
      containers:
      - name: ex-jwt-ui
        image: kwashizaki/example-golang-jwt-auth-client:v1.0.0
        ports:
        - name: ex-jwt-cl
          containerPort: 8080
        resources:
          limits:
            cpu: "0.1"
            memory: 100Mi
        env:
          - name: ORIGIN_HOST
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: origin.host
          - name: ORIGIN_PORT
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: origin.port
          - name: AMBASSADORHOST
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: ambassador.host
          - name: PORT
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: ambassador.port
example-golang-vue/example-jwt-server.yaml
apiVersion: v1
kind: Service
metadata:
  name: ex-jwt-sr
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  ex-jwt
      grpc: True
      prefix: /jwtauth.JwtService/
      rewrite: /jwtauth.JwtService/
      service: ex-jwt-sr
spec:
  type: ClusterIP
  ports:
  - name: cl-ip-ex-jwt-sr
    port: 8080
    targetPort: 50051
  selector:
    app: ex-jwt-sr

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ex-jwt-sr
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ex-jwt-sr
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: ex-jwt-sr
    spec:
      containers:
      - name: ex-jwt-sr
        image: kwashizaki/example-golang-jwt-auth-server:v1.0.0
        ports:
        - name: ex-jwt-sr-api
          containerPort: 50051
        resources:
          limits:
            cpu: "0.1"
            memory: 100Mi
        env:
          - name: AMBASSADORHOST
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: jwtserver.host
          - name: PORT
            valueFrom:
              configMapKeyRef:
                name: ex-jwt-map
                key: jwtserver.port
          - name: GOOGLE_APPLICATION_CREDENTIALS
            valueFrom:
              secretKeyRef:
                name: firebase-secret
                key: google_app_creds
        volumeMounts:
          - name: firebase-creds
            mountPath: /tmp #firebase-auth-credファイルを置く場所
            readOnly: true
      volumes:
      - name: firebase-creds
        hostPath:
          path: /Users/washizakikai/DevLocal/git/kwashi/example-k8s-ambassador/env


まとめ

ここでは、k8sによるコンテナ管理とAPI Gateway であるambassadorの設定をしめし、コンテナ間で連携したマイクロサービスの例を示した。
他のQiita記事と重複をさけ、特にk8sのマニフェストに関して説明している。

もし、VueやGolangの設定が気になる方は、記事の途中に参照した記事をぜひ見てみてください。

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

AtCoderのコンテストをスクレイピングして自動的にGoogleCalendarに追加する

はじめに

僕も9月に始めたばかりの競技プログラミングサイト,AtCoderですが,「公式のGoogle Calendarの反映が遅い!!」と前から思っていたので,だったら自分で作ってしまおうということで作ってみました.
一見難しそうに見えますが割と簡単なのでサクッと作成した流れを書いていこうと思います!

カレンダー:AtCoder Google Calendar
ソースコード:https://github.com/champon1020/sachi

カレンダーは勝手に追加してもらってかまいません!

システムフロー

一番はじめに思いつくオーソドックスなやり方かと思います.

Atcoder Calendar Flow

スクレイピング

今回はGoを使おうと思ったので、goqueryというパッケージを使用してみました.
こちらの記事とGoDocを参考にしました.
とてもシンプルで扱いやすいパッケージでした.

Google Calendar Apiの設定

コード書くよりもここが一番大変でした.
とりあえず,設定の手順は以下の通りになります.

  1. Google Api Console でプロジェクト作成
  2. Google Calendar Api を有効化
  3. OAuth 2.0の同意画面の設定
  4. OAuth 2.0のアカウントを作成 & JSONファイルをダウンロード
  5. Google Calendar Apiを使ってコードを書く
  6. アクセストークンをJSONファイルで作成

とりあえずよくわからんのでドキュメントに沿っていきました.
Google Calendar Api Sample : https://developers.google.com/calendar/quickstart/js
Goのサンプルコード : https://developers.google.com/calendar/quickstart/go

いろんな記事にもっと詳しいやり方が書いてあると思うので,ここでは簡単に説明します.

  1. Consoleに入ったら左上の方に「プロジェクト作成」蘭があるか,ドロップダウンで「新しいプロジェクト作成」とかがあると思うので,そこから流れに任せてプロジェクトを作成します.

  2. 上の検索欄で「Google Calendar」と検索するか,もしくは直接ググってみるとGoogle Calendar Api用の画面にくると思います.あとは「APIを有効化」を押せばOKです.

  3. 普通のGoogle Api Consoleに戻って「OAuth 2.0同意画面」の設定を行います.左ボックスにあるとおもいます.
    「OAuth 2.0同意画面」とは,単にアプリケーションがアカウントのGoogleサービスにアクセスするときの認証を行う画面のことです.
    名前とかは適当でいいですが,スコープだけ注意してください!(後ほど記載)

  4. Google Consoleの左ボックスの「認証情報」から,「OAuth2.0」のアカウントを作成します.作成したら右の方にあるダウンロードボタンでJSONファイルをダウンロードします.
    ※このファイルは機密情報なので必ずgitignoreするか,もしくはリポジトリ内に入れないでください!

  5. Google Calendar Apiのドキュメントにサンプルコードが載ってるので,とりあえずそれを写経しました.

  6. サンプルコードを実行してみました.すると,実行したターミナルにURLが出てくるのでそこをクリックすると,認証画面に飛びます.しかし,進んでいくと以下の画面が出ました.

google_rename.jpg

デベロッパーなので気にせず「安全ではないページに移動」から移動してみましょう.
進んでいくとトークンのようなキーが生成されるので,それをコピーして実行ターミナルにペーストします.
すると,token.jsonというファイルが作成されます.
これでアプリからGoogle Calendarにアクセスできるようになりました.

スコープについて

スコープとは,簡単に言うと「OAuth2.0でどこまでを許可対象に入れるか」みたいな感じだと思います.今回はEventを扱うだけなので以下のスコープを追加しました.

https://www.googleapis.com/auth/calendar.events

また,サンプルコードをそのまま用いてclientを作成し,そのまま追加したいイベントをGoogle Calendarにプッシュすると以下のエラーが出ます.

Error 403: Insufficient Permission: Request had insufficient authentication scopes.

OAuth2.0の同意画面側ではスコープの設定が完了していますが,肝心のサンプルコード内ではスコープを設定できていない状態でした.
そこで,サンプルコードを以下のように変更します.

func main() {
    ...

    // config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope)
    config, err := google.ConfigFromJSON(b, calendar.CalendarEventsScope)

    ...
}

これでもう一度token.jsonを作成するところから始めれば問題ないはずです!

デプロイ

僕はVPSを使用しているので,scpでサーバに送ってビルドして実行しただけです.
一応コマンドを載せて起きます.

// build
go build -o hoge

// run
./hoge &

常駐アプリなので&をつけて実行しました.

まとめ

速攻で作ったので出来の良いものではないかもしれませんが,楽しかったのでよかったです.
また,テストコードも一応作成したのですが,コンテストが毎回更新されるため,エラーが起きないかどうかくらいしかテストすることができませんでした...
こういう場合,どのようにテストを行えば良いのかわかりません...笑
勉強しなければいけませんね.

参考文献

Google Calendar Api Document : https://developers.google.com/calendar
goquery GoDoc : https://godoc.org/github.com/PuerkitoBio/goquery
Goとgoqueryでスクレイピング : https://qiita.com/Yaruki00/items/b50e346551690b158a79

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

Go歴0日だけどGo/beegoでSDK使わずにLINEBot作ってみる

はじめに

こんにちは、今回はGoの入門がてらLINEBotを作成したいと思います。

Goにはこれっしょ!となるようなWebフレームワークは存在しないようですが、筆者の浅い調べによるとGinというフレームワークが人気があるようです。
また、同様の浅い調べによってbeegoというWebフレームワークも割と人気があるとのことで。

どっちにしようか迷ったのですが、なんとなく情報が少ないbeegoの方を使うことにしました。

LINEBotの基本のキであるおうむ返しBotを作成します。

Goの書き方で何か変な部分やこっちの方がええんちゃうってのがありましたら、コメ欄にてそっと教えてください。

beego公式
https://beego.me/

今回Goの書き方は以下の記事を参考に頑張ります。
他言語プログラマがgolangの基本を押さえる為のまとめ

LINE Developerへの登録/チャネルの登録

↓ここから登録
https://developers.line.biz/ja/

色々現在の状況と異なりますが以下の記事を参考にチャネルの登録を行います
https://qiita.com/nkjm/items/38808bbc97d6927837cd

Goのインストール

僕はMacユーザーなので以下で行うインストール手順は全てMacユーザー向けです。

そもそもGoがインストールされていないのでインストールします。

$ brew install go
$ go version   #これでversionが出ればインストールOKです

beegoのインストール

https://beego.me/quickstart

go get -u github.com/astaxie/beego
go get -u github.com/beego/bee

以下を参考にパスを通します。
Beego(Golang Framework)使い方メモ

$ export GO15VENDOREXPERIMENT=1
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin
$ export PATH=$PATH:$GOROOT/bin
$ source ~/.bashrc

beegoプロジェクト作成

https://beego.me/docs/quickstart/new.md

$ bee new linebot

以下のような構成になります

linebot
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
├── tests
│   └── default_test.go
└── views
    └── index.tpl

以下でサーバーを起動できます

$ bee run

http://localhost:8080/
にアクセスして以下の画面が出れば成功です

スクリーンショット 2020-01-20 15.17.42.png

ルーティング

https://beego.me/docs/quickstart/router.md

LINEBot用に新たに以下を追加します。

router/router.go
package routers

import (
    "linebot/controllers"
    "github.com/astaxie/beego"
)

func init() {
    beego.Router("/", &controllers.MainController{})
    beego.Router("/line", &controllers.LineController{}) //追加
}

jsonの扱い

jsonの扱いについては以下の記事が参考になりました
https://qiita.com/nayuneko/items/2ec20ba69804e8bf7ca3

Goってこんなにjsonが扱いずらいものなんですね。。

PHPなら5行で書けそうな処理だね。

いや本当に。

とりあえず構造体で諸々定義していきます。

LINEAPIで使用するjsonはネストしているので、以下の記事内のパターン1で行きます。
https://qiita.com/msh5/items/dc524e38073ed8e3831b

line_controller.go
type LineMessageBodyJson struct {
  ReplyToken     string  `json:"replyToken"`
  Messages      LineMessageMessageJsons  `json:"messages"`
}

type LineMessageMessageJson struct{
  Type         string  `json:"type"`
  Text         string  `json:"text"`
}

type LineMessageMessageJsons []LineMessageMessageJson

type LineMessageFromJson struct {
  Events         []struct{
    ReplyToken   string  `json:"replyToken"`
    Message      struct{
      Text         string  `json:"text"`
    }  `json:"message"`
  }  `json:"events"`
}

こんな感じで構造体を定義しました。

コントローラーの作成

先ほどの構造体の定義も含めてコントローラーを作成します。

https://beego.me/docs/quickstart/controller.md
https://beego.me/docs/mvc/controller/controller.md

パラメータの受け取り方については以下のページが参考になります。
https://beego.me/docs/mvc/controller/params.md

line_controller.go
package controllers

import (
    "github.com/astaxie/beego"
    "encoding/json"
    "net/http"
    //"net/url"
    "bytes"
    "log"
    "io/ioutil"
)


type LineController struct {
    beego.Controller
}

type LineMessageBodyJson struct {
  ReplyToken     string  `json:"replyToken"`
  Messages      LineMessageMessageJsons  `json:"messages"`
}

type LineMessageMessageJson struct{
  Type         string  `json:"type"`
  Text         string  `json:"text"`
}

type LineMessageMessageJsons []LineMessageMessageJson

type LineMessageFromJson struct {
  Events         []struct{
    ReplyToken   string  `json:"replyToken"`
    Message      struct{
      Text         string  `json:"text"`
    }  `json:"message"`
  }  `json:"events"`
}

func (this *LineController) Post() {
  // request内容の受け取り処理
  var received_message LineMessageFromJson
  body, err := ioutil.ReadAll(this.Ctx.Request.Body)
  if err != nil {
    log.Fatal(err)
  }
  if err := json.Unmarshal(body, &received_message); err != nil {
        log.Fatal(err)
    }

  // eventごとに処理(今回は1つだけ)
  for _, event := range received_message.Events{
    message := event.Message
    //source := event.Source
    reply_token := event.ReplyToken
    text := message.Text

    //replyするデータの用意
    data := new(LineMessageBodyJson)
    data.ReplyToken = reply_token
    to_send_message := LineMessageMessageJson {
      Type: "text",
      Text: text,
    }
    var to_send_messages LineMessageMessageJsons
    to_send_messages = append(to_send_messages, to_send_message)
    data.Messages = to_send_messages
    json_data, _ := json.Marshal(data)

    //request作成
    endpoint_uri := "https://api.line.me/v2/bot/message/reply"
    req, err := http.NewRequest(
        "POST",
        endpoint_uri,
        bytes.NewBuffer(json_data),
    )
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer hogehoge") //自分のアクセストークン

    client := &http.Client{}

    resp, err_in_req := client.Do(req)
    if err_in_req != nil {
        log.Fatal(err_in_req)
    }
    log.Print(resp)
    defer resp.Body.Close()
  }
}

僕が慣れていないからかもしれませんが、GoってPostリクエストの受け取りだったり、先ほど出てきたjsonの扱いだったり、色々大変ですね。。

実装はこれで終了です!

ngrokを用いてテストする

ngrokの設定

実際に試してみます。
WebhookURLにlocalhostは指定できないのでngrokを使います。

以下の記事を参考にngrokをインストールしてください。
ngrokが便利すぎる

インストールできたら

$ ngrok http 8080

とすると

スクリーンショット 2020-01-21 1.24.45.png

このように起動できます。
これより後に出てくるngrokのURLは適宜自分のものと置き換えてください

WebhookURLの設定

MessagingAPI設定|>Webhook設定|>WebhookURL
から
https://5fb80f51.ngrok.io/lineを入力します。

テスト

これで準備は整いました!

サーバーを起動して以下のようにおうむ返しされれば成功です!

S__71688251.jpg

終わりに

僕はいろんな言語に入門する際に試しにLINEBotを作ってみるのですが、Goは一番大変でした。
GoにはSDKが用意されているので大人しくSDKを使用するべきだと感じました。
(SDK使わずにFlexメッセージとか作成しようもんなら一大苦労ですね多分。。)

Web用途に限らずこれから頑張ってGo勉強したいと思います!

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

Goでduを高速化してみた

背景

後輩: ホームディレクトリのディスク使用率が100%になったみたいです!新規でログインできなくなりやした!
ワタシ: 犯人をさがせー
後輩: dfしたけど100%ってことしかわからんとです....
ワタシ: duで調べるんやで! -sk?オプションはググって!
後輩: ググって実行したんですけど、めっちゃ時間かかって返ってこないっす....
ワタシ: 待つしかないなー\(^o^)/

ワタシだけかもしれませんが、duってオプション覚えられなくないですか?
あと、遅くないですか?

こんな出力にしたいな

ファイルパス ファイル数 容量
grepableにしたいよね〜

実装

Goにはゴルーチンという必殺の武器があるので、並列実行で早くなりますな!
https://github.com/kuritayu/infra-tools/blob/master/cmd/rapidu/main.go

ベンチマーク

環境はMac、timeの出力結果。ファイル数598909、容量129755.2 MB

du -sk /Users  0.58s user 11.63s system 57% cpu 21.387 total
/Users 598909 files 129755.2 MB     # 実際の出力結果
rapidu /Users  5.44s user 27.37s system 232% cpu 14.114 total

7秒早くなった。出力結果をシンプルにできた!

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