- 投稿日:2020-01-12T23:25:36+09:00
MarkdownでAA入力を対応してみた 【ぼくのがんがえたさいきょうの掲示板】
個人で作っている掲示板サイトがある。最近日本語対応を実装しているが、途中であることに気づいてしまった。
「せっかく和訳したところでAA貼れないなら意味ない!」
AAは日本語の掲示板の不可欠な文化である。しかし現代のパソコンやスマホではAAが正しく表示されない場合が多い。これはどうにか解決したい。
やりたいこと
私の掲示板はMarkdown記法で書き込む形だ。AAをそのまま入力すると、太字として認識されたり自動的に改行が入ったりする。2chの専ブラみたいにいい感じにAAを表示したい。
<aa>(´・ω・`)</aa>自動的にAAを認識するのは大変そうなので、AAタグを用意する。AAタグの中身をAA用のフォントに変えて、Markdown記法の
*bold*
などを無視する。無理矢理AAタグを対応する
利用しているMarkdownライブラリはrussross/blackfridayだ。諸々があって今だにv1を使ってる。
このライブラリはHTMLの入力を対応しているが、中身をMarkdown記法としてパースしてしまう。まず、フォークしよう。そして、
markdown.go
にblockTags
というマップを見てみる。markdown.go// blockTags is a set of tags that are recognized as HTML block tags. // Any of these can be included in markdown text without special escaping. var blockTags = map[string]struct{}{ "blockquote": {}, "del": {}, "div": {}, // ... "aa": {}, // ← 追加した }
blockTags
に入っているタグはブロック(パラグラフ)になり、エスケープされないとコメントが丁寧に教えてくれた。よし、"aa": {},
を追加する。これで<aa>
の中身はMarkdownにならずそのまま出力される。XSS対策としてmicrocosm-cc/bluemondayを使っている。blackfridayでMarkdownをHTMLに変換した後に、bluemondayでsanitizeをかける。bluemondayにAAタグを許可する必要がある。
var sanitizer *bluemonday.Policy func init() { sanitizer = bluemonday.UGCPolicy() // よくある書式設定用のタグなどを許可 sanitizer.AllowNoAttrs().OnElements("aa") // 属性なしの<aa>を許可 }こうしないと
<aa>
が丸ごと消されてしまう。しかし非公式のHTMLタグをそのまま出力するのは気持ち悪いので、
<aa>
を<p class="aa">
に変えよう。PuerkitoBio/goqueryはHTMLをいじるのに便利だ。上記の処理を組み合わせるとこうなる。
func renderMarkdown(content string) template.HTML { // Markdownをrender renderer := blackfriday.HtmlRendererWithParameters(commonHtmlFlags, "", "", blackfriday.HtmlRendererParameters{ // ... }) md := blackfriday.MarkdownOptions([]byte(content), renderer, blackfriday.Options{ // ... }) // XSS対策としてsanitize sanitized := sanitizer.Sanitize(string(md)) // goqueryを使ってHTMLをいじる doc, err := goquery.NewDocumentFromReader(strings.NewReader(sanitized)) if err != nil { panic(err) } // <aa>を<p class="aa">に変換 doc.Find("aa").Each(func(_ int, sel *goquery.Selection) { sel.SetAttr("class", "aa") sel.Nodes[0].Data = "p" }) // <html><body>が勝手に追加されるので省略 html, err := doc.Find("body").Html() if err != nil { panic(err) } return template.HTML(html) }※ 本当はpanicせずにerrorを返すべき
たぶん、こういう処理はmarkdownのライブラリの中でやるべきだが、blackfridayのコードはヤバイのでこっちの方が楽である。実際のコードでは
<aa>
の対応だけじゃなくてYouTubeのembedなどは似た手段で対応している。AAをいい感じに表示する
@scrpgilさんは素敵なAAフォントのまとめを提供してくれているので参考にした。めっちゃ助かる。ありがとうございます!
aahub_light1というフォントは軽くて綺麗なので使うことにした。woffファイルをS3にアップてして、キャッシュするように
Cache-Control
ヘッダをpublic, max-age=31536000, immutable
にした。AA用のCSSも作った。これも永遠にキャッシュかけたいのでメインのCSSと分けてCache-Controlを指定した。aa.css@font-face { font-family: "aahub_light"; src: url("[CDNのURL]/aahub_light.woff") format("woff"); font-display: swap; } p.aa { font-family: "aahub_light"; white-space: pre; overflow: scroll; word-break: keep-all; overflow-wrap: normal; /*font-size: 16px;*/ /*line-height: 18px;*/ }スマホなどで大きいAAがoverflowしがちなので
overflow: scroll
にした。もっとも正しい表示の仕方はfont-size
とline-height
を指定する必要があるがスマホだと大きすぎるのでとりあえず消した。それでもなんとなく大丈夫だった。結果
スマホでもAAはいい感じに表示される。やったぜ!
「ぼくのかんがえたさいきょうの掲示板」をシリーズとしてまた記事を書きたいと思うので次もよろしくお願いします!
- 投稿日:2020-01-12T23:25:36+09:00
MarkdownでのAA入力を対応してみた 【ぼくのがんがえたさいきょうの掲示板】
個人で作っている掲示板サイトがある。最近日本語対応を実装しているが、途中であることに気づいてしまった。
「せっかく和訳したところでAA貼れないなら意味ない!」
AAは日本語の掲示板の不可欠な文化である。しかし現代のパソコンやスマホではAAが正しく表示されない場合が多い。これはどうにか解決したい。
やりたいこと
私の掲示板はMarkdown記法で書き込む形だ。AAをそのまま入力すると、太字として認識されたり自動的に改行が入ったりする。2chの専ブラみたいにいい感じにAAを表示したい。
<aa>(´・ω・`)</aa>自動的にAAを認識するのは大変そうなので、AAタグを用意する。AAタグの中身をAA用のフォントに変えて、Markdown記法の
*bold*
などを無視する。無理矢理AAタグを対応する
利用しているMarkdownライブラリはrussross/blackfridayだ。諸々があって今だにv1を使ってる。
このライブラリはHTMLの入力を対応しているが、中身をMarkdown記法としてパースしてしまう。まず、フォークしよう。そして、
markdown.go
にblockTags
というマップを見てみる。markdown.go// blockTags is a set of tags that are recognized as HTML block tags. // Any of these can be included in markdown text without special escaping. var blockTags = map[string]struct{}{ "blockquote": {}, "del": {}, "div": {}, // ... "aa": {}, // ← 追加した }
blockTags
に入っているタグはブロック(パラグラフ)になり、エスケープされないとコメントが丁寧に教えてくれた。よし、"aa": {},
を追加する。これで<aa>
の中身はMarkdownにならずそのまま出力される。XSS対策としてmicrocosm-cc/bluemondayを使っている。blackfridayでMarkdownをHTMLに変換した後に、bluemondayでsanitizeをかける。bluemondayにAAタグを許可する必要がある。
var sanitizer *bluemonday.Policy func init() { sanitizer = bluemonday.UGCPolicy() // よくある書式設定用のタグなどを許可 sanitizer.AllowNoAttrs().OnElements("aa") // 属性なしの<aa>を許可 }こうしないと
<aa>
が丸ごと消されてしまう。しかし非公式のHTMLタグをそのまま出力するのは気持ち悪いので、
<aa>
を<p class="aa">
に変えよう。PuerkitoBio/goqueryはHTMLをいじるのに便利だ。上記の処理を組み合わせるとこうなる。
func renderMarkdown(content string) template.HTML { // Markdownをrender renderer := blackfriday.HtmlRendererWithParameters(commonHtmlFlags, "", "", blackfriday.HtmlRendererParameters{ // ... }) md := blackfriday.MarkdownOptions([]byte(content), renderer, blackfriday.Options{ // ... }) // XSS対策としてsanitize sanitized := sanitizer.SanitizeBytes(md) // goqueryを使ってHTMLをいじる doc, err := goquery.NewDocumentFromReader(bytes.NewReader(sanitized)) if err != nil { panic(err) } // <aa>を<p class="aa">に変換 doc.Find("aa").Each(func(_ int, sel *goquery.Selection) { sel.SetAttr("class", "aa") sel.Nodes[0].Data = "p" }) // <html><body>が勝手に追加されるので省略 html, err := doc.Find("body").Html() if err != nil { panic(err) } return template.HTML(html) }※ 本当はpanicせずにerrorを返すべき
たぶん、こういう処理はmarkdownのライブラリの中でやるべきだが、blackfridayのコードはヤバイのでこっちの方が楽である。実際のコードでは
<aa>
の対応だけじゃなくてYouTubeのembedなどは似た手段で対応している。AAをいい感じに表示する
@scrpgilさんは素敵なAAフォントのまとめを提供してくれているので参考にした。めっちゃ助かる。ありがとうございます!
aahub_light1というフォントは軽くて綺麗なので使うことにした。woffファイルをS3にアップてして、キャッシュするように
Cache-Control
ヘッダをpublic, max-age=31536000, immutable
にした。AA用のCSSも作った。これも永遠にキャッシュかけたいのでメインのCSSと分けてCache-Controlを指定した。aa.css@font-face { font-family: "aahub_light"; src: url("[CDNのURL]/aahub_light.woff") format("woff"); font-display: swap; } p.aa { font-family: "aahub_light"; white-space: pre; overflow: scroll; word-break: keep-all; overflow-wrap: normal; /*font-size: 16px;*/ /*line-height: 18px;*/ }スマホなどで大きいAAがoverflowしがちなので
overflow: scroll
にした。もっとも正しい表示の仕方はfont-size
とline-height
を指定する必要があるがスマホだと大きすぎるのでとりあえず消した。それでもなんとなく大丈夫だった。結果
スマホでもAAはいい感じに表示される。やったぜ!
「ぼくのかんがえたさいきょうの掲示板」をシリーズとしてまた記事を書きたいと思うので次もよろしくお願いします!
- 投稿日:2020-01-12T20:32:22+09:00
sqlxでのselect例
sqlxでのselect例
Select
import ( "fmt" "github.com/jmoiron/sqlx" _ "github.com/go-sql-driver/mysql" ) type Hoge { ID int `db:"id"` Name string `db:"name"` } tx, err := db.Beginx() if err != nil { fmt.Println(err) } query := ` SELECT * FROM hoge WHERE name in (?) ` names := []string{"foo", "bar"} query args, err := sqlx.In(query, names) if err != nil { fmt.Println(err) } query = tx.Rebind(query) stmt, err := tx.Preparex(query) if err != nil { fmt.Println(err) } var hoges []Hoge err = stmt.Select(&hoge, args...) if err != nil { fmt.Println(err) } . . . err = tx.Commit() if err != nil { fmt.Println(err) }Get
import ( "fmt" "github.com/jmoiron/sqlx" _ "github.com/go-sql-driver/mysql" ) type Hoge { ID int `db:"id"` Name string `db:"name"` } tx, err := db.Beginx() if err != nil { fmt.Println(err) } stmt, err := tx.PrepareNamed(` SELECT * FROM hoge WHERE name = :name `) if err != nil { fmt.Println(err) } args := map[string]interface{}{ "name": "foo" } var hoge Hoge err = stmt.Get(&hoge, args) if err != nil { fmt.Println(err) } . . . err = tx.Commit() if err != nil { fmt.Println(err) }
- 投稿日:2020-01-12T17:35:16+09:00
眺めて覚えるGo言語 その12 Map & Json
json文字列([]byte)を定義してvar adrs []map[string]interface{}形式にマーシャルします。
json.gopackage main import ( "encoding/json" "fmt" "log" ) func main() { b:=[]byte(`[{ "ID":1, "Name":"関波子", "Furigana":"セキナミコ", "Sex":"女", "Blood":"AB", "Date":"1980/07/31", "Tel":"03-3060-4716", "MTel":"090-7787-3784", "Mail":"sk@eaccess.net" }, { "ID":2, "Name":"小倉準司", "Furigana":"コクラジュンジ", "Sex":"男", "Blood":"A", "Date":"1973/10/08", "Tel":"0166-36-3522", "MTel":"", "Mail":"junzi-kokura@eaccess.net" }, { "ID":3, "Name":"西村有紀子", "Furigana":"ニシムラユキコ", "Sex":"女", "Blood":"O", "Date":"1972/12/05", "Tel":"0745-67-2723", "MTel":"090-5165-2074", "Mail":"okikuy1972@livedoor.com" } ]`) var adrs []map[string]interface{} if err := json.Unmarshal(b, &adrs); err != nil { log.Fatal(err) } for _,a := range adrs { for k,v :=range a{ fmt.Println(k,v) } fmt.Println("----------------------------------------------") } } 実行結果 go run json.go Name 関波子 Sex 女 Blood AB Date 1980/07/31 ID 1 Furigana セキナミコ Tel 03-3060-4716 MTel 090-7787-3784 Mail sk@eaccess.net ---------------------------------------------- ID 2 Tel 0166-36-3522 MTel Mail junzi-kokura@eaccess.net Name 小倉準司 Furigana コクラジュンジ Sex 男 Blood A Date 1973/10/08 ---------------------------------------------- Name 西村有紀子 Furigana ニシムラユキコ Date 1972/12/05 Mail okikuy1972@livedoor.com ID 3 Blood O Tel 0745-67-2723 MTel 090-5165-2074 Sex 女 ----------------------------------------------日本語jsonでも動作します。
jp_json.gopackage main import ( "encoding/json" "fmt" "log" ) func main() { b:=[]byte(`[{ "ID":1, "名前":"関波子", "名前フリガナ":"セキナミコ", "性別":"女", "血液型":"AB", "生年月日":"1980/07/31", "電話番号":"03-3060-4716", "携帯番号":"090-7787-3784", "メール":"sk@eaccess.net" }, { "ID":2, "名前":"小倉準司", "名前フリガナ":"コクラジュンジ", "性別":"男", "血液型":"A", "生年月日":"1973/10/08", "電話番号":"0166-36-3522", "携帯番号":"", "メール":"junzi-kokura@eaccess.net" }, { "ID":3, "名前":"西村有紀子", "名前フリガナ":"ニシムラユキコ", "性別":"女", "血液型":"O", "生年月日":"1972/12/05", "電話番号":"0745-67-2723", "携帯番号":"090-5165-2074", "メール":"okikuy1972@livedoor.com" } ]`) var adrs []map[string]interface{} if err := json.Unmarshal(b, &adrs); err != nil { log.Fatal(err) } for _,a := range adrs { for k,v :=range a{ fmt.Println(k,v) } fmt.Println("----------------------------------------------") } } 実行結果 >go run jp_json.go 名前フリガナ セキナミコ 生年月日 1980/07/31 電話番号 03-3060-4716 メール sk@eaccess.net ID 1 名前 関波子 性別 女 血液型 AB 携帯番号 090-7787-3784 ---------------------------------------------- C:\Users\hirat\go-work\go-json>go run ex3.go ID 1 性別 女 血液型 AB 生年月日 1980/07/31 携帯番号 090-7787-3784 メール sk@eaccess.net 名前 関波子 名前フリガナ セキナミコ 電話番号 03-3060-4716 ---------------------------------------------- ID 2 名前フリガナ コクラジュンジ 血液型 A 電話番号 0166-36-3522 携帯番号 メール junzi-kokura@eaccess.net 名前 小倉準司 性別 男 生年月日 1973/10/08 ---------------------------------------------- 血液型 O 生年月日 1972/12/05 電話番号 0745-67-2723 メール okikuy1972@livedoor.com 携帯番号 090-5165-2074 ID 3 名前 西村有紀子 名前フリガナ ニシムラユキコ 性別 女 ----------------------------------------------注意
type Adr struct { ID int 名前 string 名前フリガナ string 性別 string 血液型 string 生年月日 string 電話番号 string 携帯番号 string メール string }上記のようなstructを定義して
var adrs []Adr if err := json.Unmarshal(bytes, &adrs); err != nil { log.Fatal(err) }を実行すると文字通りUnmarshal(無法者)の結果が得られます。
{0 } ---------------------------------------------- {0 } ---------------------------------------------- {0 } ---------------------------------------------- {0 } ----------------------------------------------
- 投稿日:2020-01-12T16:11:14+09:00
Golangでの0-1ナップサック問題の実装
Goで0-1ナップサック問題を解く
ナップサック?ナップザック?どちらも見かけるような気がします.
英語ではKnapsack(nˈæpsæk)なのでサックなような気がします.目的・理由
最近仕事でナップサック問題で解決出来る問題と遭遇した.
仕事全体の中では非常に小さい問題ではあったがアルゴリズムが直接役に立ったことを大変嬉しく思っており,記念に初めて記事を書く.Goで書くのはGoを書きたいというささやかで非常に素朴なアピールである.
想定読者
未来のわたし
ナップサック問題とは
ナップサック問題には制約によって様々な解法パターンがあるが今回の対象となるのは,もっともオーソドックスな
0-1ナップサック問題
と呼ばれるケース.
以下に簡単な問題設定を記載する.容量$W$のナップサック1つと,$N$個の荷物$a_i (1\le i\le N)$がある.
各荷物には重さ$w_i$と価値$v_i$が設定されており,$N$個の荷物からいくつかを選びナップサックに詰め込んでいく.
ただし,ナップサックに入れる荷物の重さの総和は容量$W$以下とならなければならない.
上記の条件を満たすとき一度にナップサックに詰め込める価値の総和の最大値を求める.各荷物については
- ナップサックに入れる
- ナップサックに入れない
の2通りであるから,0-1ナップサック問題と呼ばれる.これは組合わせ最適化の問題でもある.
これを全通り試そうとすると$2^N$通りのパターンがあり,ナイーブに実装すると$N=40$程度でも現実的な時間では解くことはできない.そこで以下の擬似多項式時間で解くことが可能な動的計画法がよく使われる.動的計画法による解法
ナップサック問題は多くの場合動的計画法で解かれる.詳細は後回しにするが,先に結果を述べる.
$a_0$から$a_i (0\le i\le N)$までを使って(荷物$a_0$は荷物なしを意味する)容量$j$のナップサックに荷物を詰め込むとき詰め込める価値の総和の最大値を$dp_{i, j} (1\le i\le N, 0\le j \le W)$であるとする.このとき,$dp_{i, j}$は以下の式で与えられる.この$dp_{N, W}$が求めたいナップサック問題の解,容量Wのナップサックに詰め込める価値の総和の最大値である.
\begin{aligned} dp_{i, j} = \begin{cases} &\mathrm{max}(dp_{i-1, j}, dp_{i-1, j-w_i}+v_i)\hspace{20px} &(i\gt0, j \ge w_i)\\ & dp_{i-1, j} &(i\gt0, j \lt w_j)\\ & 0 &(i=0) \end{cases} \end{aligned}\\上記の式では,問題サイズを小さくした部分問題の解を記録しそれを利用して解を求めている.
これをもう少しみていく.${dp}_{i, j}$は荷物$a_i$まで使って容量$j$のナップサックに詰め込める価値の総和の最大値である.
このとき容量$j$のナップサックに荷物$a_{i}$を入れることを考える.
$w_i > j$であるならば,荷物$a_i$を入れることはできないため,入れられる価値の総和の最大値は$a_1$から$a_{i-1}$を使って容量$j$のナッサックにいれられる価値の最大値($=dp_{i-1,j}$)と等しい.
一方で$w_i \le j$である場合は,$a_i$を入れるか入れないかの2パターンが存在しどちらかがこの場合における最大値となる.まず,入れないときは先ほどと同様に$a_1$から$a_{i-1}$を用いて得られる最大値が値となる.つまり$dp_{i-1, j}$となる.
入れる場合は,$j-w_i$の容量のナップサックに$a_1$から$a_{i-1}$を使ってナップサックに入れられる価値の最大値に$a_i$の価値$v_i$を足したものが値となる.つまり$dp_{i-1, j-w_i} + v_i$となる.
これら2つの値を比較して価値が大きいものを選ぶことになる.これらを数式にすると上述の更新式になる.初期値として,$i=0$つまり荷物を全く使わないで得られる価値を初期条件としてやると求めたい$dp_{i,j}$を更新式を辿っていけば求めることができる.
計算量としては,時間計算量および空間計算量ともに$\mathrm{O}(NW)$ となる.
空間計算量については,長さ$M+1$の配列2つを再利用すれば$\mathrm{O}(W)$とすることも可能になる.実装
ちょうどAtCoderのDPまとめコンテストに0-1ナップサック問題があったので,コードの検証および入手力形式はこちらに準じることにした.
このコードでACを取れること確認した.package main import ( "fmt" "math" ) // mathパッケージのMaxはfloat型しかないためint用にラップしておく func max(lhs, rhs int) int { return int(math.Max(float64(lhs), float64(rhs))) } func main() { var ( N, M int64 ) // 入力 fmt.Scanf("%d %d", &N, &M) values, weights := make([]int64, N), make([]int64, N) for i := 0; i < N; i++ { fmt.Scanf("%d %d", &weights[i], &values[i]) } // 32bitだとオーバーフローする場合があるので64bit // Goはデフォルトで0で初期化される. dp := make([][]int64, N+1) for i := 0; i < N+1; i++ { dp[i] = make([]int64, M+1) } // 動的計画法部分 // 緩和式にしたがって値を更新していく for i := 1; i <= N; i++ { for j := int64(0); j <= M; j++ { dp[i][j] = dp[i-1][j] if j >= weights[i-1] { dp[i][j] = max(dp[i][j], dp[i-1][j-weights[i-1]]+values[i-1]) } } } // 出力 fmt.Println(dp[N][M]) }ちょっとした応用
今までの問題は容量W以下で価値を最大化する問題であったが,更新式を少し変更してやることで重さがちょうどWでもっとも価値が高い組み合わせというものも探せる.最大部分和集合問題を混ぜたような形になる.
$dp_{i, j}$を
dp_{i, j} = \left\{ \begin{array}{ll} -1 & ((i, j) \neq (0, 0)) \\ 0 & ((i, j) = (0, 0)) \\ \end{array} \right.で初期化してやり,更新式を以下の様に変更してやると$i$番目までの荷物を用いて重さがちょうど$j$の時の価値の総和の最大値を求めることができる.
dp_{i, j} = \left\{ \begin{array}{ll} dp_{i-1, j} & (j \lt w_i\ \mathrm{and}\ dp_{i-1, j}\ge 0\ \mathrm{and}\ dp_{i-1, j-w_i}\lt 0) \\ dp_{i-1, j-w_i} + v_i & (j \lt w_i\ \mathrm{and}\ dp_{i-1, j}\lt 0\ \mathrm{and}\ dp_{i-1, j-w_i}\ge 0) \\ \mathrm{max}(dp_{i-1, j}, dp_{i-1, j-w_i}+v_i) & (j \lt w_i\ \mathrm{and}\ dp_{i-1, j}\ge 0\ \mathrm{and}\ dp_{i-1, j-w_i}\ge 0) \\ \end{array} \right.この式だと$dp_{N, W}=-1$の場合もある(すなわちどのような組み合わせを使ってもちょうど$W$の重さにならない).
この場合の問題設定としては価値が一番大事な要素ではなく,総計重量と容量の差が小さいことが一番大事で同じ重さを実現する組み合わせの中でどれを選ぶかを価値の総和に基づいて判断するような形になる.
こちらもGoで実装してみる.ここではどの組み合わせになるのかを確認するためのトレースバックも追加している.
package main import "fmt" import "math" func max(lhs, rhs int64) int64 { return int64(math.Max(float64(lhs), float64(rhs))) } // Route トレースバック用のpair type Route struct { x, y int } func main() { var ( N, M int ) fmt.Scanf("%d %d", &N, &M) values, weights := make([]int64, N), make([]int, N) for i := 0; i < N; i++ { fmt.Scanf("%d %d", &weights[i], &values[i]) } dp := make([][]int64, N+1) // トレースバック用のテーブル trace := make([][]Route, N+1) for i := 0; i < N+1; i++ { dp[i] = make([]int64, M+1) trace[i] = make([]Route, M+1) } // 初期化 for i := 0; i <= N; i++ { for j := 1; j <= M; j++ { dp[i][j] = -1 } } dp[0][0] = 0 // traceにはどこの(i, j)から来たかの情報を保持させる for i := 1; i <= N; i++ { for j := 0; j <= M; j++ { if dp[i-1][j] >= 0 { dp[i][j] = dp[i-1][j] trace[i][j] = Route{i - 1, j} } if j >= weights[i-1] && dp[i-1][j-weights[i-1]] >= 0 && dp[i][j] < dp[i-1][j-weights[i-1]]+values[i-1] { dp[i][j] = dp[i-1][j-weights[i-1]] + values[i-1] trace[i][j] = Route{i - 1, j - weights[i-1]} } } } // 出力 fmt.Println(dp[N][M]) // DPテーブルの確認 fmt.Println(dp) pnt := Route{N, M} routes := []Route{} res := []int{} // トレースバック for pnt.x != 0 { routes = append(routes, pnt) pre := pnt pnt = trace[pnt.x][pnt.y] // 異なる重さのところから値が来ている場合は荷物iを追加したとき if pre.y != pnt.y { res = append(res, pre.x) } } fmt.Println(routes) // 重さがWになるときのベストな荷物の組み合わせの出力 fmt.Println(res) }終わりに
Goを書きたいといったが,競技プログラミングでGoを使うのは中々骨が折れるなと感じた.暗黙の型変換を少しもしてくれないので,forループの
j:=0
がint32型だとint64型の数値との比較j>weits[i-1]
ですらキャストをしないといけないのは,競技プログラミングみたいなのだと多少面倒臭い.
一方で業務で使うのには安心感があるだろうなという直観も生えた.
- 投稿日:2020-01-12T15:13:17+09:00
1byteをstringに変換
Go Tourで分からなかったため
Go Tourやってて調べても分からなかったため。実務で使うかは不明。
https://go-tour-jp.appspot.com/methods/18
もっといい方法があったら教えていただけると嬉しいです!fmt.Sprintで変換
str := fmt.Sprint(byte(255)) fmt.Println(str) // -> 255
- 投稿日:2020-01-12T15:06:22+09:00
dockerのscratchイメージ上でgolangのWebアプリを動かす際は、スタティックリンクが必要
Best practices for writing Dockerfiles (参考訳v18.09ベース)を参考に、golangで書いたWebアプリを動かそうとしたらハマったので、その記録を残します。
ざっくりいうと
- golangで書いたwebアプリ(サンプル)を、dockerのscratchイメージ上で動かそうとしたら起動で失敗
- 調べたらダイナミックリンクでビルドされており、scratchイメージではファイルが足りなかったのが原因
- スタティックリンクでビルドし直したら解決した
環境
- go 1.13.5
- docker 19.03.5
ハマるまでの流れ
"Use multi-stage builds"にマルチステージビルドを使うgolangのサンプルがあります。ビルドするときに使うイメージとリリースするイメージを分けることで、リリースイメージに余計なもの入れなくてすみ、イメージのサイズも小さくできます。
それはよさそうだと言うことで、手元にあったWebアプリをサンプルにして次のようなDockerfileを用意しました。
FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]sample-go-serverは、
hello world
を返す簡単なものです。package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, "hello world") }) log.Fatal(http.ListenAndServe(":8080", nil)) }このDockerfileをビルドします。
❯ docker build -t sample-go-server . ❯ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sample-go-server latest 0acf7d1e1ed7 42 minutes ago 7.45MBrunで起動すると思いきや次のようなエラーで失敗します。
❯ docker run -d -p 8080:8080 --name sample sample-go-server 1e097919ec5ac31839228646e2b14bd0434f56d74787d73b6afa172154829250 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ❯ docker logs sample standard_init_linux.go:211: exec user process caused "no such file or directory"
"no such file or directory"
と言われましても…。何が足りないかを言ってくれ〜?解決方法
ググったところ以下が見つかりました。ありがとうインターネッツ。
- Docker + Goバイナリでstandard_init_linux.go:178: exec user process caused "no such file or directory"と出た時の対処 - Qiita
- golangで書いたアプリケーションのstatic link化 - okzkメモ
netパッケージを含む場合はダイナミックリンクでビルドされ、それが原因でno such fileになるらしいです。
なので、さっそくこれに該当しているかを確認してみます。
ビルドで使ったalpineイメージにはfile
コマンドが入っていないため、まずそれを入れます。パッケージは Alpine Linux packagesで探せます。/bin # apk update && apk add file fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz v3.11.2-25-g58afcd742e [http://dl-cdn.alpinelinux.org/alpine/v3.11/main] v3.11.2-24-g7cfe3a1534 [http://dl-cdn.alpinelinux.org/alpine/v3.11/community] OK: 11261 distinct packages available (1/2) Installing libmagic (5.37-r1) (2/2) Installing file (5.37-r1) Executing busybox-1.31.1-r8.trigger OK: 12 MiB in 17 packages /bin # which file /usr/bin/file
file
コマンドで確認してみると、たしかにdinamically linked
となっていました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, Go BuildID=5E6Qy3Li7DELFoSZUyv5/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/wYNglNx2pwV9Z_45IRLn, not stripped参考サイトにあった
CGO_ENABLED=0
でビルドをし直してみます。FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN CGO_ENABLED=0 go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]たしかに
statically linked
に変わりました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=cD81ASWTt8bngTyIxfpe/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/rmgAW8vnYv0XAugzIkMj, not strippeddocker runでも無事起動できました。
❯ docker run -d -p 8080:8080 --name sample sample-go-server c27e29aaa697ac7131995472377bb2ff58e6a6ebe6a8174fb8a9de64efc767d5 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c27e29aaa697 sample-go-server "/bin/sample-go-serv…" 2 seconds ago Up 1 second 0.0.0.0:8080->8080/tcp sample ❯ curl http://localhost:8080 hello world参考
- 投稿日:2020-01-12T15:06:22+09:00
dockerのscratchイメージでgolangのWebアプリを動かすときは、スタティックリンクしてるかを確認
Best practices for writing Dockerfiles (参考訳v18.09ベース)を参考に、golangで書いたWebアプリを動かそうとしたらハマったので、その記録を残します。
ざっくりいうと
- golangで書いたwebアプリ(サンプル)を、dockerのscratchイメージ上で動かそうとしたら起動で失敗
- 調べたらダイナミックリンクでビルドされており、scratchイメージではファイルが足りなかったのが原因
- スタティックリンクでビルドし直したら解決した
環境
- go 1.13.5
- docker 19.03.5
ハマるまでの流れ
"Use multi-stage builds"にマルチステージビルドを使うgolangのサンプルがあります。ビルドするときに使うイメージとリリースするイメージを分けることで、リリースイメージに余計なもの入れなくてすみ、イメージのサイズも小さくできます。
それはよさそうだと言うことで、手元にあったWebアプリをサンプルにして次のようなDockerfileを用意しました。
FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]sample-go-serverは、
hello world
を返す簡単なものです。package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, "hello world") }) log.Fatal(http.ListenAndServe(":8080", nil)) }このDockerfileをビルドします。
❯ docker build -t sample-go-server . ❯ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sample-go-server latest 0acf7d1e1ed7 42 minutes ago 7.45MBrunで起動すると思いきや次のようなエラーで失敗します。
❯ docker run -d -p 8080:8080 --name sample sample-go-server 1e097919ec5ac31839228646e2b14bd0434f56d74787d73b6afa172154829250 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ❯ docker logs sample standard_init_linux.go:211: exec user process caused "no such file or directory"
"no such file or directory"
と言われましても…。何が足りないかを言ってくれ〜?解決方法
ググったところ以下が見つかりました。ありがとうインターネッツ。
- Docker + Goバイナリでstandard_init_linux.go:178: exec user process caused "no such file or directory"と出た時の対処 - Qiita
- golangで書いたアプリケーションのstatic link化 - okzkメモ
netパッケージを含む場合はダイナミックリンクでビルドされ、それが原因でno such fileになるらしいです。
なので、さっそくこれに該当しているかを確認してみます。
ビルドで使ったalpineイメージにはfile
コマンドが入っていないため、まずそれを入れます。パッケージは Alpine Linux packagesで探せます。/bin # apk update && apk add file fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz v3.11.2-25-g58afcd742e [http://dl-cdn.alpinelinux.org/alpine/v3.11/main] v3.11.2-24-g7cfe3a1534 [http://dl-cdn.alpinelinux.org/alpine/v3.11/community] OK: 11261 distinct packages available (1/2) Installing libmagic (5.37-r1) (2/2) Installing file (5.37-r1) Executing busybox-1.31.1-r8.trigger OK: 12 MiB in 17 packages /bin # which file /usr/bin/file
file
コマンドで確認してみると、たしかにdinamically linked
となっていました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, Go BuildID=5E6Qy3Li7DELFoSZUyv5/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/wYNglNx2pwV9Z_45IRLn, not stripped参考サイトにあった
CGO_ENABLED=0
でビルドをし直してみます。FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN CGO_ENABLED=0 go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]たしかに
statically linked
に変わりました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=cD81ASWTt8bngTyIxfpe/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/rmgAW8vnYv0XAugzIkMj, not strippeddocker runでも無事起動できました。
❯ docker run -d -p 8080:8080 --name sample sample-go-server c27e29aaa697ac7131995472377bb2ff58e6a6ebe6a8174fb8a9de64efc767d5 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c27e29aaa697 sample-go-server "/bin/sample-go-serv…" 2 seconds ago Up 1 second 0.0.0.0:8080->8080/tcp sample ❯ curl http://localhost:8080 hello world参考
- 投稿日:2020-01-12T15:06:22+09:00
dockerのscratchイメージでgolangのWebアプリを動かす
Best practices for writing Dockerfiles (参考訳v18.09ベース)を参考に、golangで書いたWebアプリを動かそうとしたらハマったので、その記録を残します。
ざっくりいうと
- golangで書いたwebアプリ(サンプル)を、dockerのscratchイメージ上で動かそうとしたら起動で失敗
- 調べたらダイナミックリンクでビルドされており、scratchイメージではファイルが足りなかったのが原因
- スタティックリンクでビルドし直したら解決した
環境
- go 1.13.5
- docker 19.03.5
ハマるまでの流れ
"Use multi-stage builds"にマルチステージビルドを使うgolangのサンプルがあります。ビルドするときに使うイメージとリリースするイメージを分けることで、リリースイメージに余計なもの入れなくてすみ、イメージのサイズも小さくできます。
それはよさそうだと言うことで、手元にあったWebアプリをサンプルにして次のようなDockerfileを用意しました。
FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]sample-go-serverは、
hello world
を返す簡単なものです。package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, "hello world") }) log.Fatal(http.ListenAndServe(":8080", nil)) }このDockerfileをビルドします。
❯ docker build -t sample-go-server . ❯ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sample-go-server latest 0acf7d1e1ed7 42 minutes ago 7.45MBrunで起動すると思いきや次のようなエラーで失敗します。
❯ docker run -d -p 8080:8080 --name sample sample-go-server 1e097919ec5ac31839228646e2b14bd0434f56d74787d73b6afa172154829250 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ❯ docker logs sample standard_init_linux.go:211: exec user process caused "no such file or directory"
"no such file or directory"
と言われましても…。何が足りないかを言ってくれ〜?解決方法
ググったところ以下が見つかりました。ありがとうインターネッツ。
- Docker + Goバイナリでstandard_init_linux.go:178: exec user process caused "no such file or directory"と出た時の対処 - Qiita
- golangで書いたアプリケーションのstatic link化 - okzkメモ
netパッケージを含む場合はダイナミックリンクでビルドされ、それが原因でno such fileになるらしいです。
なので、さっそくこれに該当しているかを確認してみます。
ビルドで使ったalpineイメージにはfile
コマンドが入っていないため、まずそれを入れます。パッケージは Alpine Linux packagesで探せます。/bin # apk update && apk add file fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz v3.11.2-25-g58afcd742e [http://dl-cdn.alpinelinux.org/alpine/v3.11/main] v3.11.2-24-g7cfe3a1534 [http://dl-cdn.alpinelinux.org/alpine/v3.11/community] OK: 11261 distinct packages available (1/2) Installing libmagic (5.37-r1) (2/2) Installing file (5.37-r1) Executing busybox-1.31.1-r8.trigger OK: 12 MiB in 17 packages /bin # which file /usr/bin/file
file
コマンドで確認してみると、たしかにdinamically linked
となっていました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, Go BuildID=5E6Qy3Li7DELFoSZUyv5/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/wYNglNx2pwV9Z_45IRLn, not stripped参考サイトにあった
CGO_ENABLED=0
でビルドをし直してみます。FROM golang:1.13.5-alpine AS build WORKDIR /go/src/sample-go-server COPY ./app /go/src/sample-go-server RUN CGO_ENABLED=0 go build -o /bin/sample-go-server FROM scratch COPY --from=build /bin/sample-go-server /bin/sample-go-server EXPOSE 8080 ENTRYPOINT ["/bin/sample-go-server"]たしかに
statically linked
に変わりました。/bin # file sample-go-server sample-go-server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=cD81ASWTt8bngTyIxfpe/TdC1EDiTsXrg0i0ta2Xx/49v4RWXcyEm12mEJPV8f/rmgAW8vnYv0XAugzIkMj, not strippeddocker runでも無事起動できました。
❯ docker run -d -p 8080:8080 --name sample sample-go-server c27e29aaa697ac7131995472377bb2ff58e6a6ebe6a8174fb8a9de64efc767d5 ❯ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c27e29aaa697 sample-go-server "/bin/sample-go-serv…" 2 seconds ago Up 1 second 0.0.0.0:8080->8080/tcp sample ❯ curl http://localhost:8080 hello world参考
- 投稿日:2020-01-12T14:51:52+09:00
Golang + gRPCで開発を始められるDockerfileを作る
概要
Go + gRPCで開発を行う為の環境構築方法を解説します。
環境構築はDockerを利用します。
gRPCの詳しい説明はこの記事では行いません。
想定読者
- Dockerの基礎知識をお持ちの方
- GoLangの基礎知識をお持ちの方
- Go + gRPCでの開発に興味がある方
サンプルコードをGitHubに登録してあります
以下にサンプルコードを用意してあります。
https://github.com/keitakn/golang-grpc-server
こちら がDockerfileの中身になります。
以後はこのDockerfileを作り上げるまでに実行した手順を解説しますので、 こちら を見て理解出来る方はこの記事をこれ以上読む必要はありません。
Dockerfileを作るまでの手順
Alpine 3.11系のGoLangイメージをベースにします。
- ホストOS(MacOS Catalina 10.15.2)
- Dockerの実行環境(Docker for Mac 2.1.0.5)
protobufのインストール
gRPCでは初めにインタフェース定義言語 (IDL) でAPIの定義ファイルを作成します。
ここではgRPCでデファクトスタンダードになっている Protocol Buffers を利用出来るようにします。
https://github.com/protocolbuffers/protobuf/releases から最新版ダウンロードを行いインストールを行います。
packageの最新化と関連packageのインストール
packageの最新化とコンパイルに必要なpackageをインストールします。
apk update apk add git curl build-base autoconf automake libtool
/tmp
にtar.gz
をダウンロードするcurl -L -o /tmp/protobuf.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protobuf-cpp-3.11.2.tar.gzBuild、インストールを行う
cd /tmp
で/tmp
に移動します。
tar xvzf protobuf.tar.gz
を実行し.tar.gz
を解凍します。その後
cd protobuf-3.11.2
で移動します。以下のコマンドを順番に実行します。
./autogen.sh ./configure make -j 3 make check make install補足ですが
make -j
は並行処理を行う事でコンパイルを高速化する為のオプションです。
make -j 3
は3並行処理を行うという意味になります。
お使いの環境の「CPUの数×2」を目安に調整するのが良いと思います。ちなみに、Build、インストールにはかなりの時間がかかります。
protoc --version
で以下のように表示されれば成功です。libprotoc 3.11.2protocのGo用のプラグインをインストール
protocをGoで利用する為にプラグインをインストールします。
go get -u github.com/golang/protobuf/protoc-gen-go
protocによるdocument生成(任意)
こちらは任意ですが、
.proto
からHTML形式等でドキュメントを生成出来るのでインストールしておくと良いでしょう。go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
protobufの動作確認
こちら に動作確認用のGitリポジトリを用意しました。
プロジェクトルートの
pb/
配下にdog.proto
を作成します。pb/dog.protosyntax = "proto3"; service Dog { rpc FindCuteDog (FindCuteDogMessage) returns (FindCuteDogResponse) {} } message FindCuteDogMessage { string DogId = 1; } message FindCuteDogResponse { string name = 1; string kind = 2; }プロジェクトルート(私のサンプルプロジェクトだと
/go/app
)で以下のコマンドを実行します。protoc --go_out=plugins=grpc:. pb/dog.protoそうすると
pb/dog.pb.go
というファイルが作成されているかと思います。以下のようなファイルです。(内容を一部載せています。)
pb/dog.pb.go// Code generated by protoc-gen-go. DO NOT EDIT. // source: pb/dog.proto package dog import ( context "context" fmt "fmt" proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type FindCuteDogMessage struct { DogId string `protobuf:"bytes,1,opt,name=DogId,proto3" json:"DogId,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *FindCuteDogMessage) Reset() { *m = FindCuteDogMessage{} } func (m *FindCuteDogMessage) String() string { return proto.CompactTextString(m) } func (*FindCuteDogMessage) ProtoMessage() {} func (*FindCuteDogMessage) Descriptor() ([]byte, []int) { return fileDescriptor_993586c250cd4a03, []int{0} } // 以下略ここまでの手順でGolang + gRPCで開発出来る環境が整いました。
最終的なDockerfileを載せておきます。(こちら と同様の内容です)
DockerfileFROM golang:1.13-alpine3.11 as build WORKDIR /tmp ENV PROTOBUF_VERSION 3.11.2 ENV PROTOBUF_URL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-cpp-$PROTOBUF_VERSION.tar.gz RUN set -eux && \ apk update && \ apk add --no-cache git curl build-base autoconf automake libtool && \ curl -L -o /tmp/protobuf.tar.gz $PROTOBUF_URL && \ tar xvzf protobuf.tar.gz WORKDIR /tmp/protobuf-$PROTOBUF_VERSION RUN set -eux && \ ./autogen.sh && \ ./configure && \ make -j 3 && \ make install && \ go get -u github.com/golang/protobuf/protoc-gen-go && \ go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc WORKDIR /go/app COPY . . RUN set -eux && \ go build -o golang-grpc-server && \ go get gopkg.in/urfave/cli.v2@master && \ go get github.com/oxequa/realize && \ go get -u github.com/go-delve/delve/cmd/dlv && \ go build -o /go/bin/dlv github.com/go-delve/delve/cmd/dlv FROM alpine:3.11 WORKDIR /app COPY --from=build /go/app/golang-grpc-server . RUN set -x && \ addgroup go && \ adduser -D -G go go && \ chown -R go:go /app/golang-grpc-server CMD ["./golang-grpc-server"]以前 Docker上のGoLangをリモートデバッグする という記事を書きましたが、この記事で紹介しているデバッガー等のインストールもこの
Dockerfile
で行っています。protocによるdocument生成
こちらのプロジェクト を例に説明します。
docs
配下にHTML形式のドキュメントを生成する為には下記のコマンドを実行します。protoc --doc_out=html,index.html:./docs pb/*.proto結果として
docs/index.html
が出力されます。見た目はこんな感じです。
おまけ gRPCサーバーの動作確認(grpc_cli を利用)
gRPCはcurl等で手軽に動作確認が出来ませんでしたが、gRPC command line tool を利用すると動作確認が簡単です。
Mac上にインストールします。
brew install gflags brew tap grpc/grpc brew install grpc
which grpc_cli
を実行して/usr/local/bin/grpc_cli
等が表示されればインストール出来ています。grpc_cliの簡単な使い方
以下でServiceの内容を取得します。
grpc_cli ls localhost:9998
以下のようにgRPCサーバーの内容が表示されます。
Cat grpc.reflection.v1alpha.ServerReflection次のようにメソッドを指定するとそのインターフェースを確認出来ます。
grpc_cli ls localhost:9998 Cat.FindCuteCat -lこれらのgRPCサーバーの情報を取得する為にはgRPCサーバー側で
reflection.Register
が実行されている必要があります。詳しくは こちらのコード を確認して下さい。
実際にgRPCのメソッドを呼び出す際は下記のように実行します。
grpc_cli call localhost:9998 Cat.FindCuteCat 'catId: "moko"'
おわり
以上がGolang + gRPCでの開発環境構築方法です。
最近はマイクロサービスでの開発が多いので、Golang + gRPCという組み合わせの採用例が増えているように感じます。
この記事が少しでもお役に立てたら幸いです。
今回の記事を書く為に以下の記事を参考にさせて頂きました。
- 投稿日:2020-01-12T00:00:46+09:00
golang で aws s3v4 の署名キーを作成する方法
aws s3v4 署名バージョン
aws s3 は version4 より署名プロセスが変わっています
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html署名作成の流れ
- 署名に必要なキーを作成する(本記事はここ)
- 1.で作成したキーを使って署名を作成する
各言語での署名キー作成方法
各言語の署名キー作成フローは以下の文書にまとまっています。しかしながら、
Java
,.NET (C#)
,Python
,Ruby
,Javascript
のサンプルコードはあっても、Golangのサンプルコードは存在しません
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-v4-examples.htmlそのため、本記事ではGolangで version4 用の署名取得処理のサンプルコードを載せることとします
ちなみにですが、本記事では署名キーを作成する部分の説明であり、署名作成のために前もって準備しておかなければならない処理が存在します。それについては、後日記事を執筆しようと思います。公式のサンプルを乗せて比較できるようにしていますが、さっさとGolangでの実装方法を知りたい人は、こちらから
署名に必要なキーを作成する(Java-公式サンプルコード)
公式のサンプルコードにならって説明します
言語は何でも良いのですが、Java を載せておきます
HmacSHA256
はある文字列を、あるキー(byte配列)を使ってhash化する関数です
getSignatureKey
は特定の文字列をhash化して署名を得る処理です
ある文字列をハッシュ化した[]byteをキーとして、ハッシュを重ねていくことで、最終生成物の署名を得ることができます。
version4では、
- date
- region
- service
- s3のsecretキー
を使って署名を作成しますstatic byte[] HmacSHA256(String data, byte[] key) throws Exception { String algorithm="HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes("UTF-8")); } static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception { byte[] kSecret = ("AWS4" + key).getBytes("UTF-8"); byte[] kDate = HmacSHA256(dateStamp, kSecret); byte[] kRegion = HmacSHA256(regionName, kDate); byte[] kService = HmacSHA256(serviceName, kRegion); byte[] kSigning = HmacSHA256("aws4_request", kService); return kSigning; }署名に必要なキーを作成する(Golang)
package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" ) func main() { s3Secretkey := "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" dateStamp := "20120215" regionName := "us-east-1" serviceName := "iam" signatureKey := getSignature(s3Secretkey, dateStamp, regionName, serviceName) fmt.Println(signatureKey) // f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d } func getBinaryByMakeHMAC(msg string, key []byte) ([]byte, error) { mac := hmac.New(sha256.New, key) _, err := mac.Write([]byte(msg)) return mac.Sum(nil), err } func getSignature(s3SecretKey, dateStamp, regionName, serviceName string) string { kSecret := []byte("AWS4" + s3SecretKey) kDate, _ := getBinaryByMakeHMAC(dateStamp, kSecret) kRegion, _ := getBinaryByMakeHMAC(regionName, kDate) kService, _ := getBinaryByMakeHMAC(serviceName, kRegion) kSigning, _ := getBinaryByMakeHMAC("aws4_request", kService) return hex.EncodeToString(kSigning) }サンプルコード
https://play.golang.org/p/91EMyZsbvsx公式が用意している以下のサンプルデータの場合に、出力される値を確認しながら進めるとデバッグしやすいです
key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' dateStamp = '20120215' regionName = 'us-east-1' serviceName = 'iam'署名キーを得るまでの中間値
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559' kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d' kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c' kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa' kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'サンプルコードでこの中間値を得るためには、ハッシュ化した後の[]byteを
hex.EncodeToString([]byte)
にて[]byteをhex(16進数)へと変換する必要があることに注意してください
- 投稿日:2020-01-12T00:00:46+09:00
golang で aws s3v4 の署名を作成する方法(署名キーを作成する方法)
aws s3v4 署名バージョン
aws s3 は version4 より署名プロセスが変わっています
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html署名作成の流れ
- 署名に必要なキーを作成する(本記事はここ)
- 1.で作成したキーを使って署名を作成する
各言語での署名キー作成方法
各言語の署名キー作成フローは以下の文書にまとまっています。しかしながら、
Java
,.NET (C#)
,Python
,Ruby
,Javascript
のサンプルコードはあっても、Golangのサンプルコードは存在しません
https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-v4-examples.htmlそのため、本記事ではGolangで version4 用の署名取得処理のサンプルコードを載せることとします
ちなみにですが、本記事では署名キーを作成する部分の説明であり、署名作成のために前もって準備しておかなければならない処理が存在します。それについては、後日記事を執筆しようと思います。公式のサンプルを乗せて比較できるようにしていますが、さっさとGolangでの実装方法を知りたい人は、こちらから
署名に必要なキーを作成する(Java-公式サンプルコード)
公式のサンプルコードにならって説明します
言語は何でも良いのですが、Java を載せておきます
HmacSHA256
はある文字列を、あるキー(byte配列)を使ってhash化する関数です
getSignatureKey
は特定の文字列をhash化して署名を得る処理です
ある文字列をハッシュ化した[]byteをキーとして、ハッシュを重ねていくことで、最終生成物の署名を得ることができます。
version4では、
- date
- region
- service
- s3のsecretキー
を使って署名を作成しますstatic byte[] HmacSHA256(String data, byte[] key) throws Exception { String algorithm="HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes("UTF-8")); } static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception { byte[] kSecret = ("AWS4" + key).getBytes("UTF-8"); byte[] kDate = HmacSHA256(dateStamp, kSecret); byte[] kRegion = HmacSHA256(regionName, kDate); byte[] kService = HmacSHA256(serviceName, kRegion); byte[] kSigning = HmacSHA256("aws4_request", kService); return kSigning; }署名に必要なキーを作成する(Golang)
package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" ) func main() { s3Secretkey := "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" dateStamp := "20120215" regionName := "us-east-1" serviceName := "iam" signatureKey := getSignature(s3Secretkey, dateStamp, regionName, serviceName) fmt.Println(signatureKey) // f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d } func getBinaryByMakeHMAC(msg string, key []byte) ([]byte, error) { mac := hmac.New(sha256.New, key) _, err := mac.Write([]byte(msg)) return mac.Sum(nil), err } func getSignature(s3SecretKey, dateStamp, regionName, serviceName string) string { kSecret := []byte("AWS4" + s3SecretKey) kDate, _ := getBinaryByMakeHMAC(dateStamp, kSecret) kRegion, _ := getBinaryByMakeHMAC(regionName, kDate) kService, _ := getBinaryByMakeHMAC(serviceName, kRegion) kSigning, _ := getBinaryByMakeHMAC("aws4_request", kService) return hex.EncodeToString(kSigning) }サンプルコード
https://play.golang.org/p/91EMyZsbvsx公式が用意している以下のサンプルデータの場合に、出力される値を確認しながら進めるとデバッグしやすいです
key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' dateStamp = '20120215' regionName = 'us-east-1' serviceName = 'iam'署名キーを得るまでの中間値
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559' kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d' kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c' kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa' kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'サンプルコードでこの中間値を得るためには、ハッシュ化した後の[]byteを
hex.EncodeToString([]byte)
にて[]byteをhex(16進数)へと変換する必要があることに注意してください