githubのsearchAPIを簡単にラップしたGUI作りました。
久しぶりにjqueryとか触ったらすごい懐かしい匂いがして色々つまりました。
request処理とかしててハマったところがあるので別でエントリでまとめようと思います。
コードは以下 github.com
refs: はまったところは以下でまとめてみた
githubのsearchAPIを簡単にラップしたGUI作りました。
久しぶりにjqueryとか触ったらすごい懐かしい匂いがして色々つまりました。
request処理とかしててハマったところがあるので別でエントリでまとめようと思います。
コードは以下 github.com
refs: はまったところは以下でまとめてみた
refs: io - The Go Programming Language
io.ReaderはデフォルトのReadのWrap。
ioパッケージのReaderはinterfaceとしてReadが設定されている。
type Reader interface { Read(p []byte) (n int, err error) }
文字列置換を実装する前にgoにおけるinterface埋め込みを使った抽象化で文字列置換を実装します。
Readerが標準のReadをinterfaceとして持っているので、このReaderを埋め込むことでReadメソッドに独自の処理を加えてWrapします。
※ goは同一interfaceを定義したメソッドをstructに定義することで実装の詳細をstructに生やしたメソッドに移譲することができる。
cf. io.Reader
type Reader interface { Read(p []byte) (n int, err error) }
goによるinterfaceの埋め込み
type WrapRead interface { Read([]byte)(int, error) } type WrapReader struct { wrapper WrapRead } func (w *WrapReader) Read(p []byte) (int, error){ // WrapReaderでのReadの実装の詳細を記載 }
以下と書いても同義
※ io.Readerはデフォルトの Read([]byte) (int, error)
interfaceのラップなのでわざわざinterfaceで定義し直すのは冗長。
type WrapReader struct { reader io.Reader } func (w *WrapReader) Read(p []byte) (int, error){ // WrapReaderでのReadの実装の詳細を記載 }
type RewriteWriter struct { reader io.Reader } func (r *RewriteWriter) Read(p []byte) (int, error) { buf := make([]byte, len(p)) n, err := r.reader.Read(buf) if err != nil && err != io.EOF { return n, err } return copy(p, bytes.Replace(buf, []byte("0"), []byte("1"), -1)), nil }
デフォルトのReaderを RewriteWriter
のstructにreaderという変数で扱えるように埋め込んで置くことで、 RewriteWriter
に生やしたReadメソッドでデフォルトのReadに実装の詳細を追加することができる。
置換するので、replaceした内容をもともとのbyteにコピーしています。
読み込みを確認するために以下のようなコードを書きました。
handler/main.go
func Top(w http.ResponseWriter, r *http.Request) { res, err := http.Get(fmt.Sprintf("http://%v:%v/data", host, port)) if err != nil { fmt.Printf("request get error. err: %v", err) } body := res.Body defer body.Close() io.Copy(w, &RewriteWriter{body}) } func Data(w http.ResponseWriter, r *http.Request) { var str string for i := 0; i < 10000; i++ { str = str + fmt.Sprintf("%v\n", "000") } w.Write([]byte(str)) }
10000行の文字列を置換するというものです。
動かしてみます。
$ go run main.go # serverが起動 $ curl http://localhost:8080 111 111 111 111 # 以下同様
10000行程度ならすぐに完了してしまいますが、Readメソッドは読み込むデータを一時的に保存しておくbufferの頭から順々に読み込んでいくような動作をしているようです。
全てのデータを終端記号まで読み込んでから全データを処理するわけではないみたいです。
※ 全てのデータを終端記号まで一括で読み込む場合でから使う場合には ioutil.ReadAllメソッドを使います。
このあたりは 「Goならわかるシステムプログラミング」 の io.Readerの章を確認しながら理解しました。
今回書いたコードは以下
有名な話です。が、いざ自分が体験したので備忘録としてまとめます。
上記で上げられている netcontextとcontext周りで死ぬ
というのに引っかかりました。
go1.6上で以下のようなリクエスト比較するコードを書いてました。 ※ コードはあくまでサンプルです。
url := "https://www.gooogle.com" values := url.Values{} req_1, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode())) req_2, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode())) if reflect.DeepEqual(req_1, req_2) { // requestが同値である。 } else { // requestは同値でない }
req_1
と req_2
をdeepEqualで比較して、同値性を判別したいという意図でしたが、このコードはgo1.6だと同値だと判定されますが、go1.8だと同値だと判定されません。
理由は最初に書いた netcontextとcontext周りで死ぬ
が原因だと思われます。
goのx/net/context パッケージが標準の context に入ったというのは有名です。
refs: Go 1.7 Release Notes - The Go Programming Language
net/context も標準のcontextとして扱われるようになったので、req_1
と req_2
は別々のリクエスト、すなわち異なるcontextを持っていると判定され、それを reflect.DeepEqual
にかけた場合、標準の context 違いがあるので、同値判定されません。
go1.7のcontextについては以下のブログがすごく勉強になりました。
では、requestの比較をしたい場合はどうすればいいかというと、 httputil.DumpRequest
を使います。
refs: httputil - The Go Programming Language
url := "https://www.gooogle.com" values := url.Values{} req_1, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode())) req_2, _ := http.NewRequest("POST", url, strings.NewReader(values.Encode())) // dumpは[]byte型 dump_1, _ := httputil.DumpRequest(req_1, true) dump_2, _ := httputil.DumpRequest(req_2, true) // DeepEqual if reflect.DeepEqual(dump_1, dump_2){ // requestの同値判定 } // bytes.Equal if bytes.Equal(dump_1, dump_2) { // requestの同値判定 } // stringに変換して文字列比較 if string(dump_1) == string(dump_2) { // requestの同値判定 }
DumpRequest
することで context ではなくリクエストそのものを比較出来ます。
比較方法は、以前同様 DeepEqual
を使ってもいいですし、 []byte
型に変換されるので、それに合わせて bytes
packageを使ってもいいですし、文字列に変換して文字列一致をしても同値性を取ることが出来ると思います。
IsZero()
はUnixTime = 0ではないIsZero()
を使ってもtrueを返さないrefs: time package - time - pkg.go.dev
IsZero reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC.
IsZero()は 01-01 00:00:00 UTC
の時にtrueを返します。
ここで注意するべきはtrueを返す時刻はunixtimeのスタート時刻 1970-01-01 00:00:00 +0000 UTC
を指し示すわけではないということでです。
実際の挙動を見てみます。
def := time.Time{} fmt.Printf("%v\n", def) fmt.Printf("%v\n", def.IsZero()) // output // 0001-01-01 00:00:00 +0000 UTC // true
time.Time{}
は何もない時刻をinstance化する、すなわちtimeパッケージにおける標準時刻をinstance化することですが、これの結果は 0001-01-01 00:00:00 +0000 UTC
という時刻がinstance化され、IsZero() はこの時刻のときのみtrueを返します。
udef, _ := time.Parse("2006-01-02 15:04:05 -0700", "1970-01-01 00:00:00 +0000") fmt.Printf("%v\n", udef) fmt.Printf("%v\n", udef.IsZero()) // output // 1970-01-01 00:00:00 +0000 +0000 // false
一方でコンピューターにおける時刻ゼロとはunixtimeの1番最初だと想起できるので、unixtimeのスタートした時刻に対して IsZero()
をcallすると、unixtimeのstartの時刻にもかかわらず false を返します。
timeパッケージの中身を見てみると
type Time struct { // sec gives the number of seconds elapsed since // January 1, year 1 00:00:00 UTC. sec int64 // nsec specifies a non-negative nanosecond // offset within the second named by Seconds. // It must be in the range [0, 999999999]. nsec int32 // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year // that correspond to this Time. // Only the zero Time has a nil Location. // In that case it is interpreted to mean UTC. loc *Location }
とあり、そもそもの sec = 0
の時には January 1, year 1 00:00:00 UTC.
が初期値設定されています。
IsZero()
については
// IsZero reports whether t represents the zero time instant, // January 1, year 1, 00:00:00 UTC. func (t Time) IsZero() bool { return t.sec == 0 && t.nsec == 0 }
とあるので、そもそもunixtime=0を返さないのはgoのtimeパッケージの仕様のようです。
さて、ここで困ったのがGAEでDatastore上に time.Time
型で標準時刻をinstance化した時のことです。
以下のようなstructを考えてみます。
type App struct { ID int `datastore: "ID" json: "id"` CreatedAt time.Time `datastore: "createdAt" json: "created_at"` UpdatedAt time.Time `datastore: "updatedAt" json: "updated_at"` ReleasedAt time.Time `datastore: "ReleasedAt" json: "released_at"` }
この App
Entityがcreateされた時に CreatedAt
と UpdatedAt
はそれぞれcreateされた時刻が入りますが
リリースされたわけではないので、 ReleasedAt
には何も入りません。
つまり、 ReleasedAt
のfieldには time.Time{}
が入ってくることを期待してました。
しかし実際には 1970-01-01 00:00:00 +0900 JST
という日本標準時のでのunixtime = 0の状態が入っていました。
つまり、 ReleasedAt
に一度しか値を入れたくない、みたいな要件があったときに
if !app.ReleasedAt.UTC().IsZero() { // ReleasedAtにすでに値が入っている時 } else { // ReleasedAtに初回に値が入る }
上記のような条件分岐を考慮した場合、どんなときでも else 以下に入ってしまいます。
理由は上記で述べた通り、 unixtime のスタートはtimeパッケージで IsZero 判別するときには false を返してしまうからです。
ではどうすればいいかというと、実は unixtimeの最初の状態を作り出した time オブジェクトのunixtimeを取ると 0
になります。
udef, _ := time.Parse("2006-01-02 15:04:05 -0700", "1970-01-01 00:00:00 +0000") fmt.Printf("%v\n", udef) fmt.Printf("%v\n", udef.UTC().Unix()) // output // 1970-01-01 00:00:00 +0000 +0000 // 0
これを利用して上記の条件分岐を以下のように書き換えます。
if app.ReleasedAt.UTC().Unix() != 0 { // ReleasedAtにすでに値が入っている時 } else { // ReleasedAtに初回に値が入る }
app.ReleasedAt.UTC().Unix()
とすることで、すでに ReleasedAt
に値が入ってきている場合は、 Unix()
でunixtimeに変換した時に 0以外
が入ってくる事になります。
timeパッケージにおける IsZero()
の挙動とGAEのDatastoreでデフォルトの時刻を unixtime = 0
判定を同様に考えてきて、かなりハマりました。
IsZero()
がunixtimeのstart時刻を示さないのはどうにも納得が行きませんが、timeパッケージ的にはどうしようもなさそうなので、注意しようと思いました。
このエントリを書いてから 4年以上経って言及されるとは思ってなかったですが、 いい感じに答えたが書いてあって参考にしたいなと思いました。
Goにはzero valuehttps://t.co/2NMmBdXz3M
— Hiroaki Nakamura (@hnakamur2) 2022年3月28日
という概念があってhttps://t.co/YUq3QgB5LX
でも数か所で言及されています。
で、time.IsZero()はtime.Timeがzero valueかどうかを返すんですよね。
Unix時間で0かは t.UnixNano() == 0 で判定すれば良いです。 https://t.co/UGr4fl45J8
前回書いた記事の中でオレオレroutingを実装する際に標準の net/http
パッケージだけだと足りないと書いてましたがこれ、間違いでした。
標準の net/http
パッケージだけでオレオレroutingを実装する方法は以下
main.go
package main import ( "gothub/handler" "fmt" "net/http" "github.com/labstack/gommon/log" ) const port = "8080" func main() { router := http.NewServeMux() router.HandleFunc("/", handler.Top) if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil { log.Fatal("err: %v", err) } }
handler/handler.go
pakage handler import ( "net/http" ) func Top(w http.ResponseWriter, r *httpRequest){ // serverの処理 }
router := http.NewServeMux()
で HTTP Request multiplexer
をインスタンス化し、routerとして扱う。
http requestをhandleしたいroutingのメソッドには http.ResponseWriter
と http.Request
を引数に与える。
routingのライブラリを使うことなく、標準のHTTPパッケージだけでもやりたかった、超薄いAPIを作るということは可能でした。
(golangのhttpパッケージすげぇ強力だなぁ(小並感))
goでFWに頼らず、net/http
だけで簡単なWeb Serverを立てたいと思ったので作ってみた。
まず躓いたのはroutingをどうするかということ。
net/httpパッケージを使用する場合、全てmain.goにroutingを書いてしまうことになりますが、FWの構造に則ってここはhttpリクエストをやりとりする箇所はちゃんとhandlerとしてディレクトリを用意したいと考えました。
(ginやechoを使っていたときはFW側で用意されたroutingをよしなに使ってましたが、いざ自分でroutingをかんがえると???ってなるものだなーと感じるなど(笑))
routingを設定する場合 www.gorillatoolkit.org github.com
上記のパッケージを使うのが一般的らしいということはわかっので、今回は gorilla/mux
を使用。
$ dep ensure -add github.com/gorilla/mux
$ dep ensure
$GOPATH
- src
- project_root
- main.go
- handler/
- main.go // ここに各種handlerを書いていく
main.go
package main import ( "gothub/handler" "fmt" "net/http" "github.com/gorilla/mux" "github.com/labstack/gommon/log" ) const port = "8080" func main() { router := mux.NewRouter() router.HandleFunc("/", handler.Top) router.Handle("/", router) if err := http.ListenAndServe(fmt.Sprintf(":%s", port), router); err != nil { log.Fatal("err: %v", err) } }
handler/main.go
package handler import ( "net/http" "fmt" "github.com/labstack/gommon/log" ) func Top(w http.ResponseWriter, r *http.Request) { log.Debugf("request url: %s", r.URL) w.WriteHeader(http.StatusOK) fmt.Printf("%v", r.Body) }
handlerに設定するメソッドには http.Resposewriter
と http.Request
を引数に持たせることでhandlerとして登録してrequestを処理できるようになる。
$ go run main.go // 別shellで $ curl -i http://localhost:8080 HTTP/1.1 200 OK Date: Sat, 04 Nov 2017 09:27:35 GMT Content-Length: 0 Content-Type: text/plain; charset=utf-8
ちゃんとroutingが通っていることを確認
goを使って簡易的なapiクライアントを作りたかったのですが、わざわざFWを使うほどでもないので自分で超薄いFWを作れたらなくらいのノリで書いてみましたら、結構簡単に作ることが出来ました。
FWを使っててのツラミとかはこのあたりのスライドにも書いてあるので標準の net/http
パッケージを基準にapiサーバーとか構築するのに便利だと思いました。
www.slideshare.net
golangのオフィシャル謹製パッケージマネージャ「dep」
個人プロジェクトではglideから乗り換えました。
公式の手順に則っておけば問題ないです。
Macでは brew
でインストール出来ます。
$ brew install dep
- $GOPATH - src/ - project/
depは $GOPATH/src
以下にプロジェクトのソースを管理するディレクトリを作成しておかないと動作しません。
$ cd $GOPATH/src/project $ dep init // Gopkg.tomlとGopkg.lockが作成されます。 // add $ dep ensure -add package-src-URL $ dep ensure
help
$ dep help dep is a tool for managing dependencies for Go projects Usage: dep <command> Commands: init Initialize a new project with manifest and lock files status Report the status of the project's dependencies ensure Ensure a dependency is safely vendored in the project prune Prune the vendor tree of unused packages version Show the dep version information Examples: dep init set up a new project dep ensure install the project's dependencies dep ensure -update update the locked versions of all dependencies dep ensure -add github.com/pkg/errors add a dependency to the project Use "dep help [command]" for more information about a command. //ensureのhelpの場合 $ dep help ensure Usage: dep ensure [-update | -add] [-no-vendor | -vendor-only] [-dry-run] [<spec>...] Project spec: <import path>[:alt source URL][@<constraint>] Ensure gets a project into a complete, reproducible, and likely compilable state: * All non-stdlib imports are fulfilled * All rules in Gopkg.toml are respected * Gopkg.lock records precise versions for all dependencies * vendor/ is populated according to Gopkg.lock Ensure has fast techniques to determine that some of these steps may be unnecessary. If that determination is made, ensure may skip some steps. Flags may be passed to bypass these checks; -vendor-only will allow an out-of-date Gopkg.lock to populate vendor/, and -no-vendor will update Gopkg.lock (if needed), but never touch vendor/. The effect of passing project spec arguments varies slightly depending on the combination of flags that are passed. Examples: dep ensure Populate vendor from existing Gopkg.toml and Gopkg.lock dep ensure -add github.com/pkg/foo Introduce a named dependency at its newest version dep ensure -add github.com/pkg/foo@^1.0.1 Introduce a named dependency with a particular constraint For more detailed usage examples, see dep ensure -examples. Flags: -add add new dependencies, or populate Gopkg.toml with constraints for existing dependencies (default: false) -dry-run only report the changes that would be made (default: false) -examples print detailed usage examples (default: false) -no-vendor update Gopkg.lock (if needed), but do not update vendor/ (default: false) -update update the named dependencies (or all, if none are named) in Gopkg.lock to the latest allowed by Gopkg.toml (default: false) -v enable verbose logging (default: false) -vendor-only populate vendor/ from Gopkg.lock without updating it first (default: false)
こんな感じ。オフィシャル謹製だし、今後はこちらを使っていこう。 refs: mattn.kaoriya.net
特定のModelに対して複数条件でsortしたい場合の実装方法についての考察
sortしたいStructのfield毎にLen,Swap,Lessのinterfaceを実装する。
古典的な実装方法で基本Structのfieldごとにsortで必要となるinterface3つを実装すればよい。
type Person struct{ Name Age } type PeopleByName []Person func (p PeopleByName)Len int{ return len(p) } func (p PeopleByName)Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Nameでsortする場合 func (p PeopleByName)Less (i, j int) { return p[i].Name < p[j].Name } func (p People)SortByName(){ sort.Sort(PeopleByName(p)) } type PeopleByAge []Person func (p PeopleByAge)Len int{ return len(p) } func (p PeopleByAge)Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Ageでsortする場合 func (p PeopleByAge)Less (i, j int) bool { return p[i].Age < p[j].Age } func (p People) SortByAge (){ sort.Sort(PeopleByAge(p)) } func main (){ p := []Person{ Person{ Name: "Taro", Age: 1, }, Person{ Name: "Jiro", Age: 2, }, Person{ Name: "Saburo", Age: 3 } // 名前でSort p.SortByName() // Jiro, Saburo, Taro // 年齢でsort p.SortByAge() // 1, 2, 3 }
古典的でわかりやすい反面、使わないinterfaceも定義してしまい、コード全体の見通しが悪い。
ベースとなるStructにつき一度sortのinterface3つを実装する。
複数条件でsortする場合は、ベースとなるStructを埋め込んだ別のStructを作って、その新しく条件を追加するために作成したStructに必要なinterfaceのみを追加する。
ベースとなるStructを埋め込んだ、新しいStructではsortのinterfaceを全て実装しなくても、大本のStrcutでsortが実装されていれば、全て実装する必要はない。
必要となるinterfaceのみ実装すれば、sortがcallされる時にsort条件が上書きされる。
※ 継承みたいなイメージ
type Person struct{ Name Age } type People []Person func (p People)Len int{ return len(p) } func (p People)Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Nameでsortする場合 func (p People)Less (i, j int) bool { return p[i].Name < p[j].Name }
最初にベースとなるPeople StructにNameでsortするための基準となるsortのinterfaceを定義しておきます。
次にAgeでsortするために、Ageでsortする場合はNameでsortするために定義したLessを上書きします。
type PeopleByAge struct{ People } func (b PeopleByAge) Less (i, j int) bool { return b.People[i].Age < b.People[j].Age // ここでPeopleのLessを上書きしている。 } func main (){ p := []Person{ Person{ Name: "Taro", Age: 1, }, Person{ Name: "Jiro", Age: 2, }, Person{ Name: "Saburo", Age: 3 } // 名前でSort sort.Sort(p) // AgeでSort sort.Sort(PeopleByAge{p}) }
type PeopleByAge struct{ People }
の箇所でPeople Structを埋め込んでいるので、PeopleByAgeに新しく3つ全てのinterfaceを実装しなくても良くなります。
古典的な方法はわかりやすい、かつ実装しやすいため基本➀で実装するのをベースに置きつつ、埋め込み型の方がよりsort条件を明確にでき、かつコードの記述量もすくないと思うので、使えるならStructに埋め込むパターンの方がいい気がする。
一方でsortについては➁の実装方法はベースのStructに依存することになるので、もしかしたら改修のときなどに予期せぬ影響が出る可能性がある。
go でのprivate methodをテストでcallする方法
goでprivateメソッドをテストファイルからcallする場合には、同一package内においてはファイルが異なっても、同じpackage内に存在するprivate methodを呼ぶことができる というgoの仕様を利用します。
goではrailsのrspecのようにテストをする場合に、専用のspecディレクトリを用意してもいいですが、private methodのテスト等を考慮した場合に、同一package内にテストファイルも配置することが多いです。
例えば以下のようなpackage構成になります
model user.go user_test.go
※ テストファイルへの物理的な移動距離が少なくて僕はこのテストの構成は非常に開発者に優しいと思っています。
実際にテストを書いてみます。
今回つかってリポジトリはこちら
ディレクトリ構成は以下のようにしました。
src main.go test sample.go sample_test.go
実際にテストファイルを書いてみます。
// sample.go package test import "fmt" func PublicMethod(){ fmt.Printf("public method: %s \n", "") } func privateMethod(){ fmt.Printf("private method: %s \n", "") }
// sample_test.go package test import "testing" func TestPublicMethod(t *testing.T) { t.Logf("test public method","") PublicMethod() } func TestPrivateMethod(t *testing.T){ t.Logf("test private method","") privateMethod() }
実際にテストを動かしてみます。
$ go test -run ./test public method: private method: PASS ok test 0.006s
package test内であればprivateメソッドでもcall出来ます。
こうすればprivateメソッドを簡単にテストすることが出来ます。
goのprivateメソッドをcallするところで少しハマったので、備忘録に簡単にまとめました。
以前書いたこれの続き
以前書いたgoでのhttpの書き方の内容の続編です。
Postの書き方とAutorizationヘッダーを付けたリクエストを構築するときの方法を備忘録としてまとめました。
標準パッケージはこちら
https://golang.org/pkg/net/http/
の三通りの書き方があります。
jsonFile, err := os.Open("data.json") if err != nil { // error } resp, err := http.Post("sample.com", "application/json", jsonFile)
val := url.Values{} val.Add("key", "value") resp, err := http.PostForm("sample.com", val)
client := http.Clietn{} val := url.Values{} val.Add("key", "value") req, err := http.NewRequest("POST", "sample.com", strings.NewReader(val.Encode())) if err != nil{ // request create error } resp, err := client.Do(req)
GAE上でhttpリクエストを使うときは以前記載した内容と同じで urlfetch(appengine.context)
を使います。
内容は標準のパッケージのClient型に近い。
// r は *http.Request ctx := appengine.NewContext(r) // GAEのContextを作成する client := urlfetch.Client(ctx) jsonFile, err := os.Open("data.json") if err != nil { // error } resp, err := client.Post("POST", "sample.com", jsonFile)
// r は *http.Request ctx := appengine.NewContext(r) // GAEのContextを作成する client := urlfetch.Client(ctx) val := url.Values{} val.Add("key", "value") resp, err := client.PostForm("sample.com", val)
// r は *http.Request ctx := appengine.NewContext(r) // GAEのContextを作成する client := urlfetch.Client(ctx) val := url.Values{} val.Add("key", "value") req, err := http.NewRequest("POST", "sample.com", http.NewReader(val.Encode()) if err != nil { // request create error } resp, err := client.Do(req)
Header情報を付加するには標準パッケージ、GAE、どちらにしてもClient型を使うことになります。
req, err := http.NewRequest("Post", "sapmple.com", url.Values{}) if err != nil{ // request create error } req.Header.Set("ContentType", "application/json") req.Header.Set("Authorization", "XXXXXXXX") // httpパッケージを使う場合 client := http.Client{} resp, err := client.Do(req)
req, err := http.NewRequest("Post", "sapmple.com", url.Values{}) if err != nil{ // request create error } req.Header.Set("ContentType", "application/json") req.Header.Set("Authorization", "XXXXXXXX") // GAEを使う時 // r は *http.Request ctx := appengine.NewContext(r) client := urlfetch.Client(crx) resp, err := client.Do(req)
Header情報を付与するときは、予め func NewRequest(method, url string, body io.Reader) (*Request, error)
で http.Request
型を生成しておかなければなりません。
その Request
型に対してHeader情報を付与していくことになります。
ちょっと遅刻してしまったけど、21日にAbemaTV Developer Conferenceに参加してきました。 (ほとんど個人のメモです。)
先に感想だけ書いておきます。
※ 僕向けのメモよりスライド探したほうがわかりやすいと思うので(笑)
感想としては、 Abemaのモニタリング、負荷対策の仕組みがマジですごかったこと。
AbemaTVのような大規模サービスをどう監視しているのかに、興味があったので、モニタリング関連のセッションは非常に勉強になりました。
特に、セッション中にOSS化されたpromvizはデモ含めてすごくUIがめちゃめちゃイケてました。
いやほんと超かっこいい。
AbemaTVはPrometheusを使ってたんですね。
iOSアプリもデイリーで7~8個PRマージしてるとか、開発もすごい活発に行われている印象を受けました。
AbemaTVといえば何度かダウンしたこともあったけど、その時のことも語られててました。
来月も「72時間ホンネテレビ」というどでかいトラフィックが来るイベントがあるので是非、来年のConferenceでそのあたりについても話が聞きたいなーと思います。
毎日大量のコードが変更されいる。
タスクにはストーリーポイントを付けている。 * 大雑把な付け方をしているけれども。
優先度判断については共通言語、共通指標を持っている * 優先度を決める時に、非エンジニアとのコミュニケーションも円滑になる。
レビューはしっかりルール化されている。 * レビューは活発な文化
iOSアプリをAndroidに寄せている * bottom sheetとか * スマホの大画面化に合わせて、全画面モーダルは避けれる傾向にある。 * マテリアルデザインのデザインシステムは非常に優れている。
Web * モバイルページでのアプリDL率向上について * twitterからの流入率は高い。しかし、twitterから来たユーザーのDL率が低い * twitterから流入したユーザーのDLを上げた * ちょっとtwitterからのLPを変更したくらい * エンジニアの工数をほとんど使わずに大きな効果を出せた事例
モニタリングシステムの進化 * 最初半年くらいはstackdriver * abemaはほぼすべてのレイヤーでモニタリングしたい * Prometheus + grafana * kubernetsと親和性が高い * PromQLが使いやすい * sqlライク * Exporterを作りやすい * AlertManagerがある
クラスタ全体のトラフィックの状態がわかりにくい * Prometheusのデータからデータを可視化したい。
MSが作ってGoogleが採用したので、フロントエンド界隈でtypescriptがしばらく積極的に使われて来そうな気が予感がしたのと、そろそろフロントエンドのキャッチアップしとかないとなーと感じ始めていたので...
typescriptをwebpackでコンパイルできるようにします。 https://webpack.js.org/guides/typescript/ を参考にして導入しました。
$ yarn add -D typescript ts-loader inline-source-map
ts-loader
refs: https://github.com/TypeStrong/ts-loader
$ touch tsconfig.json $ ls -la drwxr-xr-x - user 14 10 0:56 .idea drwxr-xr-x - user 14 10 0:56 dist drwxr-xr-x - user 14 10 1:28 node_modules .rw-r--r-- 640 user 14 10 1:28 package.json drwxr-xr-x - user 14 10 0:54 src .rw-r--r-- 0 user 14 10 1:29 tsconfig.json .rw-r--r-- 857 user 14 10 0:56 webpack.config.js .rw-r--r-- 142k user 14 10 1:28 yarn.lock
tsconfig.jsonにtypescriptをes5にコンパイルする設定をカリカリ書いていく。
今回は最初というのもあり、webpackのtypescript導入に記載してあるjsonファイルをそのまま使う。
※ json内でjsxを使っているが今回は使っていない
{ "compilerOptions": { "outDir": "./dist/", "noImplicitAny": true, "module": "commonjs", "sourceMap": true, "target": "es5", "jsx": "react", "allowJs": true } }
以下の用にts-loaderを入れてtypescriptをコンパイルできるようにする
const src = __dirname + "/src"; const dist = __dirname + "/dist" var webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin') module.exports = { context: src, entry: "./ts/index.ts", devtool:"inline-source-map", module: { rules:[ { test: /\.html$/, loader: "html-loader" }, { test:/\.tsx?$/, loader: "ts-loader", exclude: /node_modules/ }, ] }, resolve: { extensions:[".js", ".ts", ".tsx"] }, output: { path: dist, filename: "bundle.js" }, devServer: { contentBase: dist, port: 3000 }, plugins: [ new UglifyJSPlugin(), ] };
$ touch src/ts/index.ts
console.log("test") //のみを記載
で作ったyarnのscriptの再利用。
$ yarn run build yarn run v1.1.0 $ yarn run --config webpack ts-loader: Using typescript@2.5.3 and ~/emahiro/ts_sample/tsconfig.json Hash: bcac9e140803472acb6d Version: webpack 3.7.1 Time: 1114ms Asset Size Chunks Chunk Names bundle.js 498 bytes 0 [emitted] main index.html 182 bytes [emitted] [0] ./ts/index.ts 21 bytes {0} [built] Child html-webpack-plugin for "index.html": 1 asset 4 modules ✨ Done in 2.27s.
以前作成したものと同様の設定で webpack-dev-server
を起動し localhost:3000
にアクセスする
$ yarn run start // ※ リンクにあるブログで作成済みのコマンド Project is running at http://localhost:3000/ webpack output is served from / Content not from webpack is served from ~/emahiro/ts_sample/dist
開発者画面 -> consoleを見て test
とlogが記載されていればtypescriptはes5にコンパイルされて正常にブラウザで読み込まれている。
webpack + typescriptは想像以上に導入が簡単。
次は簡単なアプリを作ってみたい。
昨日の上記エントリへの追記
$ yarn add -D html-webpack-plugin $ yarn add -D html-loader
html-webpack-plugin
と html-loader
を加えてコンパイルの設定を更新する
const src = __dirname + "/src"; const dist = __dirname + "/dist" var webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { context: src, entry: "./js/index.js", output: { path: dist, filename: "index.min.js" }, devServer: { contentBase: dist, port: 3000 }, module: { loaders: [ { test: /\.js$/, exclude:/node_modules/, loader: "babel-loader", query: { presets:[ ["env", { "targets": { "node": "current" } }] ] } }, { test: /\.html$/, loader: "html-loader" } ] }, plugins: [ new UglifyJSPlugin(), new HtmlWebpackPlugin({ template: "./html/index.html" }) ] };
- root - src - js - index.js - html - index.html - dist - 出力先
src/html/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> </head> <body> hello world </body> </html>
$ yarn run build yarn run v1.1.0 $ yarn run --config webpack Hash: 1c4cb048a83c8c452b7b Version: webpack 3.6.0 Time: 577ms Asset Size Chunks Chunk Names index.min.js 814 bytes 0 [emitted] main index.html 195 bytes [emitted] [0] ./js/index.js 400 bytes {0} [built] [1] ./js/person.js 171 bytes {0} [built] Child html-webpack-plugin for "index.html": 1 asset [0] ../node_modules/html-webpack-plugin/lib/loader.js!./html/index.html 168 bytes {0} [built] ✨ Done in 1.71s.
※ build
は package.json
で新しくscriptsに追加したカスタムビルドコマンド
生成された index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> </head> <body> hello world <script type="text/javascript" src="index.min.js"></script></body> </html>
コンパイルした index.min.js
が script
タグに入ってちゃんと生成されている。
この状態で webpack-dev-server
を動かしてちゃんと動作するかを確認する
refs: https://github.com/jantimon/html-webpack-plugin#generating-multiple-html-files
SPAならindex.htmlだけ作成してそこにSPAの要素を追加していくのもありであるが、複数のhtmlファイルを出力したい場合がある。
そういう場合は html-webplack-plugin
はデフォでは index.html
しか出力しないが、別のファイルを新しくpluginで指定してfile名とコンパイル対象のtemplateを指定すれば良い。
// 略 plugins: [ // index new HtmlWebpackPlugin({ template: "./html/index.html" }), // その他 new HtmlWebpackPlugin({ filename: "admin.html", template: "./html/admin.html" }) ]
$ ls dist/ admin.html index.html
htmlファイルが複数出力されている
webpack一発でフロントエンドの環境が構築されるっていうのはこういうことかってわかってきた。
今回はベタなhtmlをそのままコンパイルしているがテンプレ(ejsファイルとか)をコンパイルすることも可能
仕事の中でjsの開発環境を構築する機会があったので、自前でes6をコンパイルする環境をととえてみた備忘録。
jsは動きが早くて色んなツールが出て来るなか、一定出尽くして落ち着いてきそうな感じをここ最近感じていたので、ようやく少しずつキャッチアップしていこうと思いました。
yarnとは?
facebookが開発したnext npm的なポジションのpackageマネージャー
去年10月にローンチして、徐々に使用事例が多くなってきたので自前でjsのモダンな開発環境を作るにあたって採用してみようと思ったから。
https://qiita.com/0829/items/ec5271c06f8ff0633dd3 あたりを参考にした。
npmが生成した package.json
を読み込める用に作られているので、npmとの互換性が保たれているので、正直どちらを使ってもいいと思われるが、npmより高速に動作し、依存関係やバージョン等も厳格に管理することができるため、npmでもできるが、yarnを積極的に使わない理由も弱い。
といった感想を持っている。
$ mkdir MyProject $ cd MyProject $ yarn init yarn init yarn init v1.1.0 ... success Saved package.json
対話式でプロジェクトの概要をセットアップしていく。
完了したらpackage.jsonができる
babelを使う場合は、 .babelrc
が必要なものかと思ってましたが、webpackの場合は webpack.config.js
にコンパイルの定義を書けます。
babelと一緒にes2015(es6)の構文対応できるように、 babel-preset-env
をインストールします。
$ yarn add -D babel-loader babel-core babel-preset-env
当初 babel-preset-es2015
を入れてましたが、yarn add している最中に warning babel-preset-es2015@6.24.1: 🙌 Thanks for using Babel: we recommend using babel-preset-env now: please read babeljs.io/env to update!
というwarnを受けたので、 babel-preset-env
を入れました。
refs: https://babeljs.io/env/
github: https://github.com/babel/babel-preset-env
コンパイル元のファイルはentryに書いて、1つに固めたファイルの出力先がoutput。
というわけで出来上がった webpack.config.js
はこちら
const src = __dirname + "/src"; const dist = __dirname + "/dist" var webpack = require('webpack'); module.exports = { context: src, entry: "./index.js", output: { path: dist, filename: "index.min.js" }, module: { loaders: [ { test: /\.js$/, exclude:/node_modules/, loader: "babel-loader", query: { presets:[ ["env"] ] } } ] } };
ディレクトリ構成は以下
MyProject src index.js dist index.min.js
このindex.jsにes6の構文で書いて、コンパイル後index.min.jsがちゃんと読み込まれるかを確認する
コンパイルしたファイルを難読化する。
webpackには標準でUglifyJSがあるので、これを plugins
に追加したが、defaultの難読化プラグインはコンパイル時にエラーが発生した
$ yarn run build yarn run v1.1.0 $ yarn run --config webpack Hash: 2689b764da6353bc4101 Version: webpack 3.6.0 Time: 341ms Asset Size Chunks Chunk Names index.min.js 3.11 kB 0 [emitted] main [0] ./index.js 371 bytes {0} [built] [1] ./person.js 162 bytes {0} [built] ERROR in index.min.js from UglifyJs Unexpected token: name (Friend) [index.min.js:79,6] error Command failed with exit code 2. error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
標準のuglify-jsが動かなかったので、uglifyjs-webpack-pluginを使う
refs: https://www.npmjs.com/package/uglifyjs-webpack-plugin
$ yarn add -D uglifyjs-webpack-plugin
const src = __dirname + "/src"; const dist = __dirname + "/dist" var webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin') //追加 module.exports = { context: src, entry: "./index.js", output: { path: dist, filename: "index.min.js" }, module: { loaders: [ { test: /\.js$/, exclude:/node_modules/, loader: "babel-loader", query: { presets:[ ["env", { "targets": { "node": "current" } }] ] } } ] }, plugins: [ new UglifyJSPlugin() //追加 ] };
webpackを動かす
yarn run build yarn run v1.1.0 $ yarn run --config webpack Hash: 2689b764da6353bc4101 Version: webpack 3.6.0 Time: 381ms Asset Size Chunks Chunk Names index.min.js 786 bytes 0 [emitted] main [0] ./index.js 371 bytes {0} [built] [1] ./person.js 162 bytes {0} [built] ✨ Done in 1.37s. $ cat dist/index.min.js !function(e){function t(n){if(r[n])return r[n].exports;var u=r[n]={i:n,l:!1,exports:{}};return e[n].call(u.exports,u,u.exports,t),u.l=!0,u.exports}var r={};t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,r){"use strict";var n=function(e){return e&&e.__esModule?e:{default:e}}(r(1));class u extends n.default{constructor(e){super(name)}callName(){alert(this.name)}}new u("Taro").callName()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});class n{constructor(e){this.n=e}}t.default=n}]);⏎
ちゃんと難読化されている。
webpackにはlocalにサーバーを立てるライブラリ webpack-dev-server
があるのでこちらを使う
$ yarn add -D webpack-dev-server $ yarn run webpack-dev-server //これで動作させることができる。
上記でも問題ないがscriptを作成した
"scritps": { "start": "yarn run webpack-dev-server" "build":... }
devserverはデフォルトは8080ポートで動作するが、手元のgoとパッティングするので3000番portに変更。
以下を webpack.config.json
に追加する
const src = __dirname + "/src"; const dist = __dirname + "/dist" var webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin') module.exports = { context: src, entry: "./index.js", output: { path: dist, filename: "index.min.js" }, devServer: { contentBase: dist, port: 3000 }, module: { loaders: [ { test: /\.js$/, exclude:/node_modules/, loader: "babel-loader", query: { presets:[ ["env", { "targets": { "node": "current" } }] ] } }, // 以下でhtmlの読み込みも追加する { test: /\.html$/, loader: "file?name=[path][name].[ext]" } ] }, plugins: [ new UglifyJSPlugin() ] };
サーバーを起動させる
yarn run start yarn run v1.1.0 $ yarn run webpack-dev-server Project is running at http://localhost:3000/ webpack output is served from / Content not from webpack is served from ~/emahiro/es6_sample/dist Hash: 093cadee3f8742a9e298 Version: webpack 3.6.0 Time: 2247ms Asset Size Chunks Chunk Names ... webpack: Compiled successfully.
注目すべきは以下
webpack output is served from / Content not from webpack is served from ~/emahiro/es6_sample/dist
サーブされているのは /
だけど、実際にはdistがwebから見たときのcontentのrootになる
dist index.min.js
というディレクトリ構造をしている場合、htmlからjsを呼び出すときは ./index.min.js
と直書きする。
サーバーが起動した状態で localhost:3000
にアクセスしたら alert
が発火するはず。
発火したら正常にes6がコンパイルされて動作していることになる。
今までjsの潮流が早すぎて、正直足突っ込んだら抜けられなくなりそうだったので、避けてきた分野でしたが、webpackを使うことでjsド素人でも簡単にes6をプロジェクトに取り入れることが出来ました。
色々試せそうですが、次はtypescript入れてコンパイルしてみようと思います。