20191125のGoに関する記事は13件です。

プライベートリポジトリをパスワードで限定公開するアプリを作った

GitHubのプライベートリポジトリを不特定多数に公開したい時ってありません? 私はあります、書類選考など。
不特定多数だから一人一人招待は無理だし、パスワード認証程度でいいんだけどなあと思いながら、
GitHubの設定でどうにかできないかと調べてみたんですが、ダメですね、なかったです。かなしいです。

クソみたいなリポジトリなんてどうせ誰も見ないんだからパブリックにしちゃえば?と思われるかもしれません。
パブリックはなんか嫌だし、私が作ったものはクソじゃないし、百歩譲ってクソだったとしても自分のクソを全世界に公開する趣味はないです

なので作りました。他になさそうだったので。

http://35.233.244.144/
(クリックしても飛べない場合URLをコピーしてみてください)

GitHubのリポジトリはこちら

ss.png

MineSweeper3Dのみパスワード公開してます。「unity」

これなに?

Webアプリケーションです。
使用技術は
フロント:React
サーバー:Golang echo
DB:Mongo
これはGCEのインスタンスの中のDockerで動いてます。
Webサービスではないので自分のリポジトリに適用させる場合はサーバー立ててCloneして云々が必要です。

なぜWebサービスにしなかったのか

これはGitHubのアクセストークンを使用し、プライベートリポジトリを取得してきています。
悪意のある運営者であれば、サービスを使用した人のプライベートリポジトリを取得できます。
それって気持ち悪くない? 私だったら利用しません。なのでWebサービスにはしませんでした。

どのように動いているのか

GitHub APIではアクセストークンを使用してリポジトリのファイルを取得できます。トークンは生成する必要があります。
https://api.github.com/repos/ユーザー名/リポジトリ/contents?access_token=トークン
みたいな。最初のディレクトリしか出ないので全て取得するにはディレクトリかを判別して再帰的に繰り返す必要があります。

サーバーを立てる前にGolangで対象のリポジトリをクローリングするところから始まります。
./repo -repo yourrepo -expire 2020-02-02 -password yourpassword
こんなコマンドをサーバー内で実行します。
これはプロジェクトのファイル一覧を再帰的に取得してDBに入れています。

あとはサーバーを立ててパスワード認証をして、認証に成功したらファイルを表示させてます。
ファイルは逐一GitHub APIを介してます。
用途としては、プライベートリポジトリの完全なミラーが目的ではなく、パスワード認証して限定公開が主目的だからです。
コマンド実行時のディレクトリ構造を保存し、そのパスをもとにファイルにアクセスしています。

全体的に難しいことはしてません。

ss2.png

まとめ

本当に技術的に気になりましたらソースを見てください。
私のようにプライベートリポジトリを限定公開したいという人がいましたら、これを使うなり、使わなくともこれが参考になればと思い、記事をしたためました。
調べても同様のものが見つからなくて絶望した過去の私のような人間が一人でも救われるのであれば幸いです。

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

【Go】日時を文字列に変換する時によく使うフォーマット指定

はじめに

しょっちゅう「あれ?Golangで日付を文字列に変換する時のフォーマットってどんなんだっけ?」となって毎回調べているので、個人的によく使うやつを自分用のメモとして残しておきます。

フォーマットの詳しい指定方法については以下の記事がわかりやすくまとまっています。
本記事では細かい解説はしません。

よく使うフォーマット

年月日時分秒の指定方法さえ覚えておけば大体なんとかなると思います。

日時形式 フォーマット
yyyy-mm-dd 2006-01-02
yyyy-m-d 2006-1-2
HH:MM:SS 03:04:05
H:M:S 3:4:5
yyyy-mm-dd HH:MM:SS 2006-01-02 03:04:05

サンプルコード

package main

import (
    "time"
)

func main() {
    t := time.Date(2019, time.February, 9, 8, 7, 4, 0, time.UTC)
    // => 2019-02-09 08:07:04 +0000 UTC

    /*
     * 日付
     */

    // yyyy-mm-dd
    t.Format("2006-01-02") // => "2019-02-09"

    // yyyy-m-d
    t.Format("2006-1-2") // => "2019-2-9"

    /*
     * 時間
     */

    // HH:MM:SS
    t.Format("03:04:05") // => "08:07:04"

    // H:M:S
    t.Format("3:4:5") // => "8:7:4"

    /*
     * 日付 + 時間
     */

    // yyyy-mm-dd HH:MM:SS
    t.Format("2006-01-02 03:04:05") // => "2019-02-09 08:07:04"
}

おわりに

改めてちゃんと調べてみて気づきましたが、月日時分秒の順番で12345になってるんですね。
そう考えると簡単に覚えられるな。。。まとめた意味なかったかも。。。

参考

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

リリースまで至らなかった個人開発サービスのコードを全公開して反省してみる【Nuxt + Go】

これはなに

これはDeNA20卒内定者エンジニアによるアドベントカレンダーDeNA 20 新卒 Advent Calendar 2019の記事として書かれています。

はじめに

僕は趣味の一環でWebサービスを作ったり作ろうとしたりしています。ちょうど一年程前に企画・開発を始めたのですが、リリースまで至らなかったサービスがあったことを思い出したのでこれを機にコードを全公開して振り返ってみることにします。

公開したコードはこちらになります。

https://github.com/tockn/emukone_public

自分で実装しておきながらすごく無責任なのですが、正直どういう思想で実装していたのかその詳細はもう忘れてしまっているので、コードを読んで思い出しながら書く形になります。

なぜリリースしなかったのか?

まずはこれです。当時DDDやクリーンアーキテクチャといったソフトウェアアーキテクチャに興味があり、どうせ実装するなら試したことない構成で色々やってみようとしていました。しかしこれが罠で、要件に合っていないオレオレな酷い構成で作り始めてしまい、実装すればするほどモチベーションが下がり、最終的に死んでしまいました。悲しいですね。

どんなサービスだったのか?

ズバリ、「アーティストとライブハウスをマッチングをするサービス」です。高校時代バンド活動をしていた経験から思いつきました。お金儲けだけを中心に考えている質の低いイベントを淘汰し、本当に良い時間・空間を作り上げようと真剣になっている素晴らしいイベントが生き残る世界を実現しようとしたサービスでした。

ざっくり使用技術とか

フロントエンドはNuxt.jsで、バックエンドはGoのよくある構成です。そもそもローカルで開発したまま死んだのでインフラまでは考えてません。
UIフレームワークとして一部SemanticUIを使いました。
データベースとしてMySQLを、マイグレーションにはsql-migrateを使いました。
ローカル開発環境にdocker-composeを使っていました。
CircleCIを使ったCI/CD環境も一応整えていた気がします。

DB設計

MySQLWorkbenchでモデリングしたものがまだ残っていたので貼っておきます。

image.png

ユーザーが、アーティストである「アーティストユーザー」と、イベント主催専用ユーザーの「ブッカーユーザー」に別れていたので、それに合わせた設計になっています。
このDB設計は非常に苦しんだ覚えがあります。READMEにこんな一言が添えられていました。。。

image.png

(どうせ個人開発なので問題ないけど。)

フロントエンド(Nuxt.js)

動作

どんな感じの見た目で動作をするのか紹介してみます。例としてアーティストユーザーのプロフィールの編集画面を晒します。

Youtubeのリンクを貼ると埋め込み形式に変換してくれる機能なんかも作りました。

構成

.
├── assets
├── components
│   ├── atoms
│   │   └── AtomButton.vue
│   ├── molecules
│   │   └── MolContentCard.vue
│   ├── organisms
│   │   └── OrgProfileHeader.vue
│   └── pages
│       └── PageArtistProfile.vue
├── layouts
├── middleware
├── node_modules
├── pages
├── plugins
├── static
└── store

Atomic Designの概念を取り入れたコンポーネント構成になっています。

コンポーネントとして切り出す作業ですが、初めから部品を切り出して組み合わせるのではなく、まず初めに一つのコンポーネントでページ一枚を作りました。
そしてそのページを実装していく上で再利用することになった(なりそう)な部品を切り出していくことで、最終的にAtoms, Molecules, Organismsとして構成していきました。

Atomsはその名の通り原子のようなそれ以上分解できないコンポーネントとして、Moleculesは複数のAtomsを組み合わせることで実装されるよう分解していきました。
Atoms, Moleculesはサービスのドメイン情報を一切含めないようにし、他サービスでも利用できるような疎結合なものにすることを心がけるようにしました。
そして、OrganismではAtoms、Moleculesを利用してドメイン情報の入った再利用するコンポーネントとして構成しました。

図示すると以下のような感じです。

image.png

サーバーサイド(Go)

構成

オレオレなレイヤードアーキテクチャになっています。

.
├── docs
│   └── swagger
├── domain
│   ├── entity
│   ├── repository
│   └── service
├── infrastructure
│   └── repository
│       ├── mysql
│       └── s3
├── interface
│   └── web
│       ├── handler
│       ├── middleware
│       └── router
├── migrations
│   └── mysql
├── registory
└── usecase

図示すると以下のような形です。

image.png

構成についての感想

正直、これがとにかく失敗でした。結局今回の実装の大部分って普通のCRUDなんですよねー。それをこんな形で過度にレイヤ化したり抽象化していってしまうと、普通のCRUDの実装に膨大な時間がかかってしまいます。当たり前ですが。

テストコードも殆ど書けていないので、レイヤ化の意味が殆どありません。

おまけにdomain/entityのstructにjsonタグを書いてしまっているので、レイヤードにすら作れていません。APIレスポンスの形式を変更するためにドメインエンティティーを触らなければいけないのは、レイヤードアーキテクチャの意味を成してないですよね。

domain/entity/user.go
type User struct {
    ID              string        `json:"id"`
    Name            string        `json:"name"`
    IconURL         string        `json:"icon_url"`
    Identifier      string        `json:"identifier"`
    HeaderImageURL  string        `json:"header_image_url"`
    MetaDescription string        `json:"meta_description"`
    Tags            []*UserTag    `json:"tags"`
    Locations       []*Location   `json:"locations"`
    WebsiteURLs     []*WebsiteURL `json:"website_urls"`
    UserImages      []*UserImage  `json:"user_images"`
}

とは言っても、これでAPIレスポンスのためのstructを別のレイヤに生やすと、entity.Usermysql.User間に加えてさらにresponse.User間を変換するマッパーのコードを書くことになるので、しんどさが増していたと思います。
実装の大部分が普通のCRUDなのにこんな構成にしてしまうのは、失敗以外の何物でもないです。これなら他の言語やフレームワークを使ってMVCだけで作った方が断然良いです。

swaggo

interface/web/handlerを覗くとわかるのですが、swaggoを使って自動でswaggerの生成なんかもやっていました。以下のようにコメントで書くだけでswaggerを自動生成してくれます。便利〜

interface/web/handler/user.go
// GetUserMeta godoc
// @Summary  ユーザーのメタ情報(概略)を取得する
// @Description
// @Accept  json
// @Produce  json
// @Param userID path string true "user id"
// @Success 200 {object} entity.UserMeta
// @Router /users/{userID}/meta [get]
func (h *user) GetUserMeta(c *gin.Context) {
    id := c.Param("userID")
    du, err := h.usecase.ShowMeta(id)
    if err != nil {
        ErrorResponse(c, err)
        return
    }
    c.JSON(http.StatusOK, du)
}

開発を振り返って

コードを読んでその時考えていたことを思い出しながら記事を書いていましたが、今なら絶対やらないような事をたくさんしていて恥ずかしくなりました。

振り返れば、個人でパブリックにリリースして使ってもらったサービスは一つだけで、しかも初めてWebプログラミングを学んだ勢いで作りリリースしたものでした。
それ以降様々な概念を学び、より良いコードを書けるように、より良い設計ができるように励んできましたが、逆にその知識が足枷となり「こんなコードじゃダメだ」「こんなアーキテクチャはクソだ」と実力が理想を上まらず、落ち込み、結果モチベーションがなくなりリリースできない。。。その繰り返しでした。

ではなぜ初めてのサービスはリリースできたのか?と考えてみると、自分の技術に対して一切期待をしていなかったからだと思います。初めてだし仕方ないや〜の精神を貫いた結果、爆速実装と爆速リリースができました。「動いてるからヨシッ!」を肯定するわけではないのですが、結局どんなに素晴らしいコードで、アーキテクチャで、思想を持って作っていたとしても、リリースしてユーザーに触ってもらわない限りは何の意味もありません。

Done is better than perfect

本当に良い言葉だと思います。
新しく何かを作り始めるときは、とにかく「作りきる事」を第一に考えて手を動かしていこうと心に刻みました。

(改めて見てみると、ファイル数すごいw)
image.png

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

【Go】SpaceCloudでMySQL/GraphQLサーバを立ち上げる

本記事で触れないこと

  • GraphQLに関する解説
  • Rest APIとの比較に関して
    RestAPI GraphQL 比較等のキーワードでggると、参考になる記事がヒットすると思います

Space Cloudについて

  • フロントエンドからGraphQL(またはREST API)を呼び出すためのAPIサーバ
  • MySQL + GraphQLサーバを立ち上げる場合の有力候補
    prismaもかなり有名(比較/検証予定)
    ※ Postgressの場合はhasura/graphql-engineが人気みたい
  • Goで書かれており、クラウドネイティブのルールに従って水平方向にスケーリングしてくれるらしい

対応DB

  • MongoDB
  • PostgreSQL、およびPostgreSQL互換のデータベース(CockroachDB、Yugabyteなど)
  • MySQLおよびMySQL互換データベース(TiDB、MariaDBなど)

事前準備

Docker環境の用意

  • 公式のQuick startに従い、docker-composeで構築します
  • MySQL5.7バージョンを明示的に指定 + my.cnfの設定を上書き + DBデータの永続化を設定しています
docker-compose.yaml
version: "3.7"
services:
  space-cloud:
    image: spaceuptech/space-cloud
    ports:
      - "4122:4122"
      - "4126:4126"
    links:
      - testdb
    restart: always
    environment:
      ## The DEV environment lets you skip login in Mission Control
      DEV: "true"
      ## Change the login credentials of Mission Control (Admin UI)
      # ADMIN_USER: "admin"
      # ADMIN_PASS: "123"
      # ADMIN_SECRET: "some-secret" # This is the JWT secret used for login authentication in Mission Control
  testdb:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: testdb
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./testdb/data:/var/lib/mysql
      - ./testdb/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./testdb/sql:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
mysql/my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4

DBスキーマの作成

以下の要領でMySQLのスキーマを作成しておく
(docker-composeで作成したMySQLコンテナにログイン + MySQLに接続 + スキーマ作成)

$ docker-compose exec testdb bash

root@04ee656af514:/# mysql -u root -p root

mysql> create database testdb;
Query OK, 1 row affected (0.01 sec)

mysql> exit

Space Cloud操作

プロジェクト作成

任意のプロジェクト名を入力し、MSQLの項目を選択
image.png
※ キャプチャを撮り忘れたのでこちらから転載しています
(実際の操作ではMYSQLを選択しています)

DB接続設定

メニューのDatabaseからEdit Connectionを選択し、ダイアログ内にroot:root@tcp(testdb:3306)/を入力
image.png
※ こちらもキャプチャを忘れたので、先ほどと同じく転載画像です

テーブル作成

Add a tableボタンから、任意のテーブル名+テーブル定義を記載する
スクリーンショット 2019-11-25 16.19.30.png

create users table

type users {
  id: ID! @primary
  phone: String!
}

create post table

後のselect joinクエリのサンプルで利用したいので、もう一つテーブルを作っておく
先ほど作成したusersテーブルを「外部キー参照」したい場合は、以下のように@relationディレクティブを指定する

type post {
  id: ID! @primary
  title: String!
  users_id: users @relation
}

クエリ発行

メニューのExplorerから直接クエリの実行が可能
スクリーンショット 2019-11-25 16.27.25.png

INSERT

mutation {
 insert_users(
   docs: [
    { id: "1", phone: "09011112222"},
    { id: "2", phone: "09033334444"}
  ]
 ) @mysql {
   status
 }
}
mutation {
 insert_post(
    docs: [
      { id: "1", users_id: "1", title: "test_title1" },
      { id: "2", users_id: "1", title: "test_title2" },
      { id: "3", users_id: "2", title: "test_title3" },
      { id: "4", users_id: "2", title: "test_title4" }
    ]
 ) @mysql {
   status
 }
}

SELECT

単一テーブルのSELECT
query {
 users (
   where: {id: "1"}
 ) @mysql {
   id
   phone
 }
}
複数テーブルのJOINクエリのサンプル
query {
 users @mysql {
   id
   phone
   post(
     where: {users_id: "users.id"}
   ) @mysql {
     title
   }
 }
}

UPDATE

mutation {
 update_users (
   where: { id: "2"},
   set: {phone: "08077778888"}
 ) @mysql {
   status
 }
}

課題

クエリで日本語等のマルチバイト文字を送信した際に、Network error: Failed to fetchのエラーが出てしまったので、mysql/my.cnfで文字コードの設定などチューニングの必要がありそう

参考

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

nuxtで作った静的ファイルをgolangのechoでホスティングする。

背景

機能の内容は言えませんが、外部APIを利用した既存の機能の簡易版のツールを作りたい。そういった依頼が舞い込んできました。
すでにその機能は提供されていますが、画面仕様のもと多くの項目の入力が必要で、客先でさくっと処理させるには不向きでした。
そのため、既存機能の登録機能のみを切り出してほしいとのことでした。
二つ返事で了承。
今後のことを考えフロントエンドは別プロジェクトにし、APIは既存のものを利用。
外部のAPIはフロントエンドでのスクリプト読み込み後のAjax送信とサーバーサイドでのポストの処理があり、特定のホスト名と特定のIPにしか対応してませんでした。
それは困った。
ということで、nuxtで静的サイトとしてgenerateしたものを既存のプロジェクトでホスティングさせてしまえば、ホスト名とIPの課題は解決するということでやり方を書いておきます。

※ソースは抜粋しか載せられないのでその点はご理解ください。

流れ

  1. nuxtでプロジェクトを作成しgenerateすることで静的サイトへ
  2. golangとechoでできた既存システムでgenerateした静的サイトをホスティング
  3. リリース方法を検討していい感じにリリースできるようにする。

nuxtでプロジェクトを作成しgenerateすることで静的サイトへ

  • ソースを載せられないのでいい感じにご自身で実装してください。

静的サイトへgenerate

  • なんのこっちゃない。あまりやったことないがとりあえずlocalで
yarn generate
  • generate?めんどくさいのでpackage.jsonをいじって
package.json
"gen": "nuxt generate",
yarn gen
  • distに静的なファイルが出力されました。 静的なファイルっていうくらいなので、要はHTMLを生で書いたのと同じ状態ですよね?ということで、ブラウザでこのファイルを開く
  • おや?スクリプト(画面のイベント)が発火してない?そんなことある? どうやらnuxt generateしても、ルート情報が正しくないと、生成されたスクリプトを読み込めないらしい。 この場合だと当然、/hoge/hoge/index.htmlでアクセスしてる。少し調べるとわかりますが、どうやらgithubpageやnetlifyやS3でホスティングしないと正常に動かないらしい。 なるほど。

じゃあ、当社で使っているbacklogで「ブラウザで直接開く」なるものがあるので、まずはそいつでホスティングさせてみましょう。

  • backlog「ブラウザで直接開く」で開いても正しく動かない。
    なるほど。URL見ればわかるが、これはHTMLをただブラウザで開いてるだけか。。。ホスティングサービスとは別物。。。
    gitと名乗る以上極力githubに寄せてほしいところだが、できないものはできないのでどうしようもない。

  • じゃあ、githubでpage作るか。
    はい、そうです。金払わんとできない。金はない。やめる。

  • じゃあ、いったんnetlifyで。
    いける!どうやらホスティングさせてあげれば正しく動くみたい。ということが分かったので、nuxtはこんなところにしておく。

  • ホスティングする際のエントリーポイントをルートに追加
    http://hostname/nuxtprojectで受ける予定なので、nuxt.config.jsに以下を追加

  router: {
    base: '/nuxtproject' 
  }

golangとechoでできた既存システムでgenerateした静的サイトをホスティング

  • まずはgitから落としてプロジェクトへぶっこむ
cd echoproject
git clone <nuxtprojectURL>

echoのプロジェクト直下にぶっこむ。
depやglideで入れたかったのですが、なかなかてこずってできなかったので、直接ぶっこむ。

  • 次にgenerateする
cd nuxtproject
yarn gen

後で、distもリポジトリにぶっこむつくりにしますが、いったんはこんな感じでOK.

  • ホスティングする

非常に簡単。やっぱりFWって素晴らしい。

e.Static("/nuxtproject", "nuxtproject/dist")

これだけでOK。

  • nuxtprojectを編集した際は毎回cloneしてgenerateする必要があるよ。

こっからいろいろ考える。

  • パッケージ化しようとしたけど、うまくできなかったので、以下の流れで本番のモジュールに組み込む
  1. distをリポジトリ保存する。
  2. echoprojectのデプロイ時にcloneする。
  3. 環境により変更すべき値を考慮する。

distをリポジトリ保存する。

  • .gitignoreでdistを外し、yarn generateした後にpushする。
    さすがに毎回手でやるのはだるい。本当ならビルド環境用意してそいつにやらせてあげればいいのだけれども、サーバー用意するのがめんどいうえ、金がない。
    なのでローカルで必ずやるようにする。

  • どうやらgitの機能にHookがあるらしい。
    コミット前、プッシュ前様々なタイミングでフックができるらしいが、今回はコミットする前を選択。
    .git/hooks/pre-commitにコマンド書いとけばいいらしい。

#!/bin/sh
yarn
yarn gen
git add .

exit 0
  • インストールして静的なファイルを生成、できたファイルを管理対象にして終わり。pushは自分でやる これで常にdistが最新される。

echoprojectのデプロイ時にcloneする。

本来ならここでビルドすればよいのだが、dokerのイメージにnuxtとnodeとnpmとyarnを入れるのは避けたかったので、クローンするだけ。その際今回のnuxtprojectはパブリックなリポジトリではないので、いじいじする。
gitはnetrcの認証情報をもとにリポジトリからとってくることができるらしい。じゃあ簡単だ。
echoprojectにechoproject_nutrcをまずは作ります。

# 自分の情報を設定してね。
machine <git_host>
login <git_user_id>
password <git_user_pwd>

これをdockerfileで ~/.netrcにしてやればOK

RUN cp echoproject_nutrc ~/.netrc

環境により変更すべき値を考慮する。

当然開発環境と本番環境は分かれてまっせということ。
echoprojectではENVという環境変数で振り分けてますが、nuxtはどうしましょう。
よし、どちらもgenerateしてホスティングするdistの名称を変えればOKじゃね?ということでそのようにします。

  1. nuxt.config-dev.jsを作る

  2. nuxt.config-dev.jsでgenerateの際の出力フォルダを指定

  generate: {
    subFolders: false,
    dir: './dist_dev'
  },
  1. package.jsonに下記を追加
  2. "gen-dev":"nuxt generate --config-file nuxt.config-dev.js",
    
  3. .git/hooks/pre-commitにyarn gen-devを追加

これでdistとdist_devができるので後はechoprojectでdistをホスティングするのかdist_devをホスティングするの指定してあげればOK。

非常にむりやりではありますが、今回はこのようにしましたとさ。

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

Golangで「特定のpackage以外の全てのpackageに対してtest」を行う方法

TL;DR

go test `go list ./... | grep -v 除外したいパッケージへのpath`

解説

go test の後ろにはテストしたいパッケージ名を書いていく。(全実行したい場合は go test ./...)

go test internal/hoge internal/fuga

go list を実行すると次のようにパッケージのリストを出力してくれるので

github.com/seya/test/server/internal/hoge
github.com/seya/test/server/internal/fuga

除外して欲しいものをgrepで除外して渡してあげればok

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

golangの数値型とmysqlの整数型

TL;DR

unsignedだったらunsignedで合わせる。
not unsignedだったらnot unsignedで合わせる。
アンマッチの場合はきっちりオーバーフローするので回避したい。
intは甘え。

mysqlの整数型

リンク先参照
https://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html

golangの整数型

リンク先参照
http://golang.jp/go_spec#Numeric_types

ざっくり対応

mysql golang
tiny int int8
int int32
big int int64

unsignedはunsigned同士で対応。

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

Golangのテンプレートエンジンを使い、日付・時刻のフォーマット指定

はじめに

Golangのテンプレートにおいて、日付のフォーマットを指定する方法。

方法

Golangのテンプレートでは、{{ .変数名 }}のような形で、テンプレートに渡されたデータにアクセスすることができる。
たとえば、time型の変数名: nowが渡されたと仮定すると、以下のようにフォーマットを指定することができる。
(何も指定しないと、2009-11-10 23:00:00 +0000 UTCのような形式となる。)

・YYYY/MM/DD形式で指定したい場合

.now.Format "2006/01/02"

・YYYY年MM月DD日 hh時mm分ss秒形式で指定したい場合

.now.Format "2006年01月02日 15時04分05秒"

のように記載すればよい。

まとめ

つまり、.Format "~"を用いると日付・時刻を指定可能である。

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

Sliceの値を構造体に表す方法

初めまして、初投稿となります。
最近Goを初めて約2ヶ月なのですが、まだまだ慣れない
特に配列が苦手だった私にとっては「arrayとsliceってなんぞや?」で
3日間苦しむことも…(´・ω・`)マジツレェ

なぜsliceと構造体を使おうとしたか?

開発している時に複数の値を入力して、
Googleのdatastoreに構造体の値を出力させるものを作ろうとしていました。

いざsliceを使って何個も値を入力できるようにしたのです。
「よしできた!( ・∇・)」と思ってdatastore見たら
構造体の一箇所にsliceのデータがギッシリ詰まってましたΣ(・□・;)

[{1 1}{2 4}{3 9}{4 16}{5 25}]みたいな感じで

もう摩訶不思議アドベンチャーです
こういうので引っかかったのは私だけじゃないはずと思いたい…

結果的にどうしたか

一度作ったsliceを別の構造体に挿入してみたって感じです
まずは簡単に構造体とsliceを作ってみます

package main

import (
    "fmt"
)

type Slice struct {
    Singular int //単数
    Power    int //累乗
}

type Store struct {      //sliceの値を入れる構造体
    Singular_answer int 
    Power_answer    int
}

func main() {
    slice := []Slice{}   //構造体Sliceをsliceにします
    for i := 1; i <= 5; i++ {
        singular := i
        power := i * i
        fmt.Println(singular, power)
        /*
            1 1
            2 4
            3 9
            4 16
            5 25
        */

        //appendでsliceの中にsinfularとpowerの値がそれぞれ入る
        slice = append(slice, Slice{
            Singular: singular,
            Power:    power,
        })
    }

    fmt.Println(slice)
    //[{1 1} {2 4} {3 9} {4 16} {5 25}]と値が入っていることが確認できた!

とりあえずこれで構造体からsliceにできましたね(´・ω・`)フゥ…
しかしこれでgoonを使って書き込みしたら私みたいになります

じゃあどうするべきか

    //ここからが解決策
    //rangeでsliceに入っている値の数ループさせます
    for _, input := range slice {
        slice_input := Slice{
            Singular: input.Singular,
            Power:    input.Power,
        }
        fmt.Println(slice_input)
        /*
            {1 1}
            {2 4}
            {3 9}
            {4 16}
            {5 25}
        */
    }
}

このようにあらかじめ用意しておいた構造体に
もう一度突っ込んで上げることでスッキリしました…

datastoreに突っ込む時はgoon.Putをループに突っ込んで上げると
一回のループで一個ずつ入ってくれるはず(´・ω・`)

以上!!

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

protobufのenumをgolangの型(int32)に変換する

protobuf で定義した enum を golang handler 内で int32型 に変換したかった時
「protobuf enum int 変換」みたいなワードでササっとググって出てこなかったのでメモとして残しておきます。

結論としては、単純に変換したい型で囲ってあげればよい。
type(enum type)

[protobuf]

service User {
  rpc PostUser(PostUserRequest) returns (User) {}
}

enum SexType {
  OTHER  = 0;
  MALE   = 1;
  FEMALE = 2;
}

message User {
  int64 id = 1;
  string name = 2;
  SexType sex = 3;
}

message PostUserRequest{
  string name = 1;
  SexType sex = 2;
}
[golang handler]
func GetUser(ctx context.Context, req *user.PostUserRequest) (*user.User, error) {
    var sex int32
    sex = int32(req.SexType)
    ... something ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Golang]構造体でフィールドの初期値をnilにするには、そのフィールドをポインタ型にすれば良い件

はじめに

golangでよく使う構造体ですが、特定のフィールドの初期値を0やfalseではなく、nilにしたい場合ってありませんか?
ポインタ型で指定することで、簡単にできるので共有しておきます。

いきなり結論のコード

package main
import "fmt"

type a struct {
    valueA int
}

type b struct {
    valueB *int
}

func main(){

    var sa a
    var sb b 

    fmt.Println(sa,sb) // => {0} {<nil>}
}

解説

1つ目の構造体では普通にint型を指定、2つ目の構造体ではintのポインタ型を指定しています。

それぞれの構造体
type a struct {
    valueA int
}

type b struct {
    valueB *int
}

下の通り、宣言だけしてそのまま実行すると、初期値が出力されます。

初期値を指定
    var sa a
    var sb b 

    fmt.Println(sa,sb) // => {0} {<nil>}

出力結果が、2つの構造体で違います。
1つ目の構造体は、初期値が0なのに対して、2つ目の構造体は初期値がnilになっています。

使用例

もしかしたら、あまり違いがないと感じられる方もいるかもしれませんが、実はこれはとても大きく違います。
たとえば、以下のような構造体で、Userの値を管理したい場合。

使用例:うまくいかないパターン
type User struct {
    lastname  string
    firstname string
    age       uint64    
}

lastnameは「鈴木」、firstnameは「太郎」、ageはいったん空けておきたい…なんて場合があったとします。

使用例:User情報を入力
    var u User

    u.lastname = "鈴木"
    u.lastname = "太郎"

実はこれで実行すると、u.ageにはint型の初期値である0が入ってしまいます。
こうなると、このユーザーが本当に0歳のなのか、それとも年齢が分からないので空けていたユーザーなのかが、わかりません。
これはまずい。

こんなときに、

使用例:うまくいくパターン
type User struct {
    lastname  string
    firstname string
    age       *uint64    
}

このようにポインタ型で指定してあげると、u.ageには初期値nilが入るので、とても便利。
0歳か不明なのか分からない…といったことが無くなります。

年齢が不明のときの構造体
&User{
    lastname : "鈴木",
    firstname: "太郎",
    age      : nil,
}
0歳のときの構造体
&User{
    lastname : "鈴木",
    firstname: "太郎",
    age      : 0,
}

bool型もnilにできる

ちなみに、フラグとして使うことも多いbool型でも、初期値をfalseにすることなく使えたりします。
試してみて下さい。

さいごに

最後まで読んで頂いてありがとうございます。
今日は、構造体の初期値をnilにする方法について、共有致しました。
また、不備があればコメント頂けると嬉しいです。

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

io.Readerをすこれ

0. なんで io.Reader?

去年のアドベントカレンダーではGo4までだったのが今年はなんとGo7までできており、Goへの関心が高まっているのはいちGo好きとしてはうれしい限りです。Goの良さは色々なところにあります、それは例えばシンプルな言語仕様だったり、標準ライブラリの充実度だったり、様々なサポートツール(go getgo vetgoimportsgorename など)だったり。こういった側面はしばしばGoの良さとして語られますし私自身もそれには同意します。一方でそれらのわかりやすい利点の裏に隠れてしまっている影の立役者がいると思っています。そう、それこそがio.Readerです。io.Reader はその使い方を正しく理解するだけでGoのインターフェースという仕組みの強力さがわかる素晴らしいインターフェースのお手本であると思っています。

1. io.Readerって?

一言で言ってしまえばバイト列を読み出すためのインターフェースです。Goを書いたことがある方はどこかで一度はコードの中で io.Reader に触れたことがあると思います。たとえその覚えがなくても io.Reader はGoを書いていれば様々な場所で出てきます。例えばファイル入力のとき、f, err := os.Open(filename) の返り値 fvar f *os.File という型ですが、この*os.Fileio.Reader インターフェースを持っています。他に頻出する例としては http.ResponseBody メンバが io.ReadCloser というインターフェースを持っていますがこれは io.Readerio.Closer の複合的なインターフェースです。

io.Readerインターフェース

Goの標準ライブラリの中で io.Reader は以下のように定義されています(コメント略)。

type Reader interface {
    Read(p []byte) (n int, err error)
}

初めてこの定義を見る方は「え、たったこれだけ?」と思われるかもしれませんが、これだけです。 Read([]byte) (int, error) というシグネチャを持ったメソッドを実装しているありとあらゆる型が io.Reader に該当します。これで io.Reader がGoの様々な場面で出てくるという主張に納得していただけるのではないかと思います。

同時にそういった方々にとってこのインターフェースを見ただけでは直感的に Read メソッドがどういった振る舞いをするのかは自明ではないでしょう。多くのライブラリは Read メソッドを叩かなくて済むように様々な仕組みを提供しているため裏で何が行われているのかを知る機会があまりないのではないかと思います。例えばファイルを読むときは *bufio.Scanner を使って一行ずつ読み込むでしょうし、HTTPのレスポンスボディは ioutil.ReadAll に渡して全部まるっと読んでしまえばよかったりするわけです。シンプルなユースケースであればごそっとバイトスライスに読み込んでしまえばよさそうですが、巨大なファイルを開く場合やストリームから継続的にデータを読み出すような使い方をする場合は io.Reader を使わざるを得ないので io.Reader を介するAPI設計は重宝されます。

Readメソッドの振る舞い

では具体的に Read メソッドが何をするか(することを期待されるか)を手続き的にご説明します。

  1. 与えられたバイトスライス p []byte を先頭から埋めていく。
  2. 埋まったバイト数 n と、埋める過程で発生したエラー err を返す。
  3. n < len(p) の場合、err != nil である可能性がある。
  4. (0, nil) を返すことは非推奨

たとえばファイルから先頭 m バイトを読みたい場合は以下のように記述します。

f, err := os.Open(filename)
p := make([]byte, m) // 1. スライス確保
n, err := f.Read(p) // 2. Read実行
if m < n { // 3. 返り値処理
    log.Fatalf("%dバイト読もうとしましたが、%dバイトしか読めませんでした\n", n, m)
}
if err != nil { // 4. エラー処理
    log.Fatalf("読込中にエラーが発生しました:%v\n", err)
}
  1. Read メソッドは可能な限り渡されたスライスの全長を埋めることを期待されるため、あらかじめ読みたいバイト数分のサイズを持ったスライスを作成しておきます。
  2. スライスの実態はポインタなのでこれをそのまま Read メソッドに渡すと中でこのスライスの中身を埋めてくれます。
  3. Readでエラーが発生していても n > 0 バイト埋められている可能性があるため先にその処理を行います。Goは多くの場合真っ先にエラー処理をすることが多いですが、Read メソッドの返り値処理はその数少ない例外と言えるでしょう。ここでは要求したバイト数が読めなかったため log.Fatalf を呼んでいますが、場合によっては p[:n] でなにかの処理をすることも想定できるでしょう(バッファリングなど)。
  4. 最後にエラーを処理します。このときn < m でも err == nil の可能性があることと、 n == 0 && err == nil であっても EOF ではないことに注意しましょう(この組の返り値が非推奨になっている所以です)。

2. io.Readerの良さ

io.Reader というインターフェースと Read メソッドの振る舞いをご理解いただいた上でなぜ io.Reader が素晴らしいのかを語っていきます。前述の通り io.Reader はGoのあらゆる場面で出てくるインターフェースですが、その理由は圧倒的な汎用性にあります。このことを説明するのにもってこいなのが標準ライブラリに実装されている bufio.Reader です。

bufio.Readerとは

io.Reader がインターフェースであるのに対して bufio.Readerio.Reader を満たした構造体です。パッケージ名の bufio が示唆する通り bufio.Readerio.Reader オブジェクトにバッファリングの機能を実装します。公式ドキュメントも以下の通り:

Reader implements buffering for an io.Reader object.

バッファリングの恩恵についてはこちらのGopherConの発表をご覧ください。ここでは発表の中で使われている単語数カウントのプログラムを借りつつ io.Reader の便利さの面にアプローチしていきます。まず単語数カウントのプログラムを作成していきます。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "unicode"
)

var p [1]byte

func readbyte(r io.Reader) (rune, error) {
    n, err := r.Read(p[:])
    if n > 0 {
        return rune(p[0]), nil
    }
    return 0, err
}

func main() {
    filename := os.Args[1]
    f, err := os.Open(filename)
    if err != nil {
        log.Fatalf("cannot open file %q: %v", filename, err)
    }
    defer f.Close()

    words := 0
    inword := false

    for {
        r, err := readbyte(f)
        if unicode.IsSpace(r) {
            if inword {
                words++
            }
            inword = false
        } else {
            inword = true
        }

        if err == io.EOF {
            break
        }

        if err != nil {
            log.Fatalf("read failed: %v", err)
        }
    }

    fmt.Printf("%q: %d words\n", filename, words)
}

リンク先の発表のコードとは異なる部分がいくつかあります。

  1. io.Reader から1バイト読み込む際にエラーがあってもそのバイトをチェックする仕組みを追加
  2. 単語の定義を wc コマンドと統一

ベンチマークのためにMoby Dickのテキストを使用します。

$ go build slow_wc.go
$ time ./slow_wc moby.txt
"moby.txt": 215822 words
./slow_wc moby.txt  0.61s user 0.76s system 97% cpu 1.404 total
$ time wc -w moby.txt
  215822 moby.txt
wc -w moby.txt  0.01s user 0.00s system 77% cpu 0.019 total

wc の100倍くらい時間がかかっています。これは *os.FileRead メソッドが呼び出されるたびにシステムコールが発生しているのが原因なので、バッファリングによってパフォーマンスの改善が期待されます。先程のコードを次のように変更します。

--- slow_wc.go  2019-11-24 03:40:14.000000000 +0900
+++ fast_wc.go  2019-11-24 03:40:17.000000000 +0900
@@ -1,6 +1,7 @@
 package main

 import (
+   "bufio"
    "fmt"
    "io"
    "log"
@@ -26,11 +27,13 @@
    }
    defer f.Close()

+   b := bufio.NewReader(f)
+
    words := 0
    inword := false

    for {
-       r, err := readbyte(f)
+       r, err := readbyte(b)
        if unicode.IsSpace(r) {
            if inword {
                words++
$ go build ./fast_wc.go
$ time ./fast_wc moby.txt
"moby.txt": 215822 words
./fast_wc moby.txt  0.03s user 0.01s system 87% cpu 0.045 total

wc の2倍程度の実行時間まで落ちました。コードの変化としてはファイルオブジェクトを bufio.Reader で包んで readbyte 関数に渡すオブジェクトを変更しただけですがここまでの差が出てきます。bufio.Reader の中ではデフォルトで4096バイトごとに読み込みを行うので *os.FileRead メソッドの呼び出しが readbyte の呼び出し4096回ごとに1回になったと捉えられます。

ここでポイントなのは readbyte 関数に一切手を加えなくて良かったことです。よく知っている方は bufio.Reader には ReadByte というメソッドがあるのでわざわざ readbyte を使う必要がないじゃないかと仰るかもしれませんがそれはその通りです(※というかむしろこの例では unicode.IsSpace を呼んでいるので本来は ReadRune を使うべきです)。しかしその議論は本質的ではなく、ここで主張したいことは、io.Reader を受け取ることを前提に設計されたAPIは非常に頑強であるということです。

上記の安全性も含めて記述するのであれば私であれば以下のようなコードを書くでしょう。

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
    "unicode"
)

func wordcount(r io.Reader) (int, error) {
    words, inword := 0, false
    b := bufio.NewReader(r)

    for {
        c, _, err := b.ReadRune()

        if unicode.IsSpace(c) {
            if inword {
                words++
            }
            inword = false
        } else {
            inword = true
        }

        if err == io.EOF {
            return words, nil
        }

        if err != nil {
            return 0, err
        }
    }
}

func main() {
    filename := os.Args[1]
    f, err := os.Open(filename)
    if err != nil {
        log.Fatalf("cannot open file %q: %v", filename, err)
    }
    defer f.Close()

    words, err := wordcount(f)
    if err != nil {
        log.Fatalf("read failed: %v", err)
    }
    fmt.Printf("%q: %d words\n", filename, words)
}

このように単語数カウントを wordcount という io.Reader を受け取る関数にしてしまうことで、ファイルだけでなくHTTPのレスポンスボディを含むありとあらゆる io.Reader に適用できるようになります。このように io.Reader をうまく組み込んでいくことで io.Reader を扱う様々なライブラリとシームレスに連携することができます。その1つの例が golang.org/x/text/transform です。

io.Readerへの操作

シンプルな例として io.Reader から読み出す際に改行を落とすようなケースを考えます。golang.org/x/text/transform 内の transform.Reader を使うと io.Readertransform.Transformer を適用して様々な変換を行うことができます。文字単位で扱う場合には golang.org/x/text/runes 内に便利な transform.Transformer がいくつか定義されているためこちらを利用します。

func IsNewline(r rune) bool { return r == '\n' }

func RemoveNewline(r io.Reader) io.Reader {
    s := runes.Predicate(IsNewline)
    t := runes.Remove(s)
    return transform.NewReader(r, t)
}

たったこれだけであらゆる io.Reader 型のオブジェクトに RemoveNewline を適用するだけで改行をすべて落とす io.Reader を生成できます。

io.Readerは強力

ここまでお読みいただいたらもうお気づきかと思いますが、bufio.Readertransform.Reader などは io.Reader を加工しますがそれ自体がまた io.Reader になっています。そのため、その他世の中に存在するありとあらゆる io.Reader を要求する仕組みと連携することができるのです。このように io.Reader は非常にシンプルで役割が明快でありつつ強力なよくデザインされたインターフェースなのです。

手前味噌にはなりますが、私が個人的に開発しているパーサコンビネータライブラリ pars では内部状態を表す pars.Stateio.Reader を受け取って必要なときに必要なだけそこから読み出してパーサを適用する設計になっています。Goで書かれたパーサコンビネータライブラリの先駆者として直接のデザインキューとなっている vektah/goparsify やそのインスピレーションとなっている prataprc/goparsec などがありますが、いずれも文字列やバイトスライスを受け取るAPI設計になっています。今私が開発しているバイオインフォマティクスの分野では非常に巨大なファイルを扱うことがよくあるため、こういったAPIは微妙に使い勝手が悪いのです。加えて、vektah/goparsify では空白を無視する仕組みがパーサの状態の中に実装されていますが、io.Reader を受け取る設計にすることでその必要はなくなります。文字列リテラル内の空白以外をすべて落とすような io.Reader のマニピュレータを作って pars.State に渡してあげれば、特別な仕組みをパーサ状態内に作り込むことなく簡単に空白を除いてパーサを適用するといったこともできるのです。

3. まとめ

io.ReaderRead(p []byte) (n int, err error) というメソッドを持った標準ライブラリに実装された特段何のひねりもない実にシンプルなインターフェースです。その反面、io.ReaderGoという言語の哲学インターフェースという機能の強力さをその身に宿した最強のインターフェースだと思っています。ここではカバーしませんでしたが対となるインターフェースである io.Writer と合わせて「Goを語る上で欠かせない、言語仕様の外にある最も重要な概念」だと言ってしまっても過言ではないというのが私の持論です。

Goのインターフェースを使いこなすのは非常に難しいですが、コミュニティとしても広く受け入れられて使われている io.Reader を積極的に使ってみることでその利便性に気がつけるという側面もあるのではないかと思います。Go初心者から脱却する第一歩としてぜひ io.Reader を使い倒してみてください。

  1. io.Reader を知り
  2. io.Reader を使い
  3. io.Reader で作りましょう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の標準パッケージにないシステムコールを使う

はじめに

Qiita 初投稿です。

Go の golang.org/x/sys には、OS のシステムコールの Wrapper 関数が定義されていますが、全てが網羅されているわけではありません。

例えば、windows package を見てみると、WSARecv() などはありますが、WSASocket() がない。

https://godoc.org/golang.org/x/sys/windows#WSAEnumProtocols

本項では、これを自前で定義する方法について述べます。

検証環境

  • Windows 10
  • go version go1.13.3 windows/amd64

Syscall の構造

まず、WSARecv() がどのように定義されているか見ていきます。
WSARecv() の定義は golang.org/x/sys/windows/zsyscall_windows.go にあります。

func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) {
    r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0)
    if r1 == socket_error {
        if e1 != 0 {
            err = errnoErr(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

はい、謎の関数 syscall.Syscall9() が現れました。
意味不明すぎて考えることをやめたくなりますが、恐れることはありません。ファイル先頭に次のようにある通り、これは自動生成コードです。

// Code generated by 'go generate'; DO NOT EDIT.

関数定義

同ディレクトリの syscall_windows.go に生成する関数の定義があります。

//sys   WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv

長いので細かく分けて見ていきます。

//sys

system call のコード生成であることを表す。このコメントを、後述の mkwinsyscall が拾う。

WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error)

定義する関数シグネチャ。WSABuf とか Overlapped とかの型は、windows package で定義されている。

[failretval==socket_error]

system call が戻り値 socket_error を返したときにエラーとする。socket_error は windows package の package private な定数。

const socket_error = uintptr(^uint32(0))

MSDN より

If no error occurs and the receive operation has completed immediately, WSARecv returns zero. In this case, the completion routine will have already been scheduled to be called once the calling thread is in the alertable state. Otherwise, a value of SOCKET_ERROR is returned

= ws2_32.WSARecv

上述の関数を、ws2_32.dll 内の WSARecv 関数にマッピングする。

コード生成

生成コマンド定義は mksyscall.go にあります。

//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go

//go:generate go run までは、Go generate の書式。

golang.org/x/sys/windows/mkwinsyscallzsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go から //sys の定義を拾い上げて、zsyscall_windows.go を生成しているようです。

自前で WSASocket() を定義してみる

仕組みはわかったので、自前で WSASocket() を定義してみます。

import "golang.org/x/sys/windows"

const (
    wsaprotocol_len    = 255
    max_protocol_chain = 7
    invalid_socket     = ^windows.Handle(0)
)

type GROUP uint32

type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

type WSAPROTOCOLCHAIN struct {
    ChainLen     int32
    ChainEntries [max_protocol_chain]uint32
}

type WSAPROTOCOL_INFO struct {
    ServiceFlags1     uint32
    ServiceFlags2     uint32
    ServiceFlags3     uint32
    ServiceFlags4     uint32
    ProviderFlags     uint32
    ProviderID        GUID
    CatalogEntryID    uint32
    ProtocolChain     WSAPROTOCOLCHAIN
    Version           int32
    AddressFamily     int32
    MaxSockAddr       int32
    MinSockAddr       int32
    SocketType        int32
    Protocol          int32
    ProtocolMaxOffset int32
    NetworkByteOrder  int32
    SecurityScheme    int32
    MessageSize       uint32
    ProviderReserved  uint32
    Protocols         [wsaprotocol_len + 1]uint16
}

//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinsys.go winsys.go
//sys WSASocket(af int32, tp int32, protocol int32, protocolInfo *WSAPROTOCOL_INFO, g GROUP, flags uint32) (socket windows.Handle, err error) [failretval==invalid_socket] = ws2_32.WSASocketW

なにやら構造体やら色々でてきましたが、これらは C の構造体やマクロ定義を移植したものです。(この辺の情報を一々調べて定義するのが面倒。。。)

ここで出てきた WORD, DWORD, int の対応は次の通り。

C Go
WORD uint16
DWORD uint32
int int32

コード生成

//go:generate を記述した package のディレクトリで次のコマンドを実行すると、zmysys.go が生成されます。

$ go generate

実際に使ってみる

正常ケースとエラーケースを試してみます。
まずは正常ケース。

sock, err := WSASocket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, nil, 0, 0)
fmt.Println(sock, err)
// => 344 <nil>

正しくSOCKETが生成され、エラーも発生していないようです。

次にエラーケース。

const badArg int32 = 9999
sock, err := WSASocket(badArg, badArg, badArg, nil, 0, 0)
fmt.Println(sock, err)
// => 18446744073709551615 The support for the specified socket type does not exist in this address family.

不正な値を与えたので、WSAESOCKTNOSUPPORT のエラーが返ってきました。
このメッセージは FormatMessageWで取得されたものです。

エラーコードは syscall.Syscall<N>() の3番目の戻り値として返ってきます。その実装は runtime のアセンブラで定義されていてコードを追いきれませんでしたが、GetLastError() の結果を返してきている模様。(たぶんこの辺)
本来、WSAXXX() のエラーコードは WSAGetLastError() で取得するものですが、WSAGetLastError()GetLastError() の Wrapper であるがため、問題なく動いているようです。

https://stackoverflow.com/questions/15586224/is-wsagetlasterror-just-an-alias-for-getlasterror

まとめ

標準package で定義されていない system call も、golang.org/x/sys/windows/mkwinsyscall を使えば、比較的簡単に呼び出し可能。

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