『Goプログラミング実践入門』をやっていく会
Goプログラミング実践入門
お知らせ
読書仲間も募集中です!
「一緒に本を進めようぜ!」的な気持ちが湧いたら、https://twitter.com/mohirara まで連絡もらえると嬉しいです(DMでもメンションでもなんでも!)
公式情報
写経リポジトリ
各章の雑まとめ、興味あるとこないところ
1章
- 概観の話
- 何かしらのWebプログラミングやったことあるなら、さらりと読み流すくらいでよさそう
第2章
- 驚きの写経ビリティの低さ!
- あとまわし
第3章
- サーバー側の実装の入門的なやーつ
- リクエストの処理とかはやらない(それは4章)
- ポイントっぽいところ(ここ押さえれば無敵な気がする)
- マルチプレクサを理解する
- 他の言語などでRouterとかDispatcherと呼ばれるやつでよさげ
- リクエストURLと何かしらのメソッド(ハンドラ)を紐付ける担当
- 似たような名前の構造体やインタフェース、関数を区別する
-
ServeMux
を理解するとサードパーティ(gorilla/mux
)もスイスイ使える
- マルチプレクサを理解する
第4章
- リクエストを受け取ったあとの話
- HTTPのことがわかっていると早いと思う
- HTTPのことは書籍にはそれほど書いてない
-
curl
とかhttp
コマンドが使えると便利(特にcurl
)
-
net/http
パッケージの強さをちょっと体感できる -
go doc
でひたすら調べていくといい感じ(この章に限らないけど)
第5章
- テンプレートエンジン
- あんまりテンション上がらないのであとまわし
- HTMLにテンションが上がらないだけっぽい
- コード生成にはかなり使えそう
-
text/template
とインタフェースが同じっっぽいので、やる価値は高いと思う(ただ、HTMLにテンションが上がらないw)
第6章 データの記憶
- メモリで保持
- テキストファイルで保持
- バイナリファイルで保持
- データベースで保持、そしてSQL
- DBとSQLは8章(テスト)でも使う
- GoでのSQL
- GoのORM
- これは時代が変わっているので、他のORMありそうな気がする
- 質問&調査ポイント
第7章 XMLとJSON
- XMLの利用
- JSONの利用
- JSONは8章(テスト)でも使う
- そして、知りたいところ
-
embed
との違いが気になるところ
第8章 テスト
- テスト。もっとも興味がある章の1つ。
- Webプログラミング要素としては、HTTPハンドラらへん、DB操作、JSON操作でいけそうな雰囲気
- 逆に言えば、それ以外のところ(テンプレートエンジンとかXMLとか)はSKIPでよさそう
第9章 ゴルーチン
- いっちばん興味があるところ
- 並行処理を理解する上で、Webプログラミングを題材にするのはめっちゃ有益だと思うから
第10章 デプロイ
- 本がちょっと古め(日本語版は2017年)なので、現代との差異が気になるところ
- とりあえず写経するかーという印象
net/http
第3章 -
net/http
ライブラリを使ってサーバーを起動する章 - 次の4章では、リクエスト内容の処理が中心になるらしい
net/http
ライブラリ内の構造体の棲み分け図解(p.60 図3-1)
- これは便利な図解。
- https://golang.org/pkg/net/http/ をみるだけだとみえにくいからね
http.Server
構造体を直接使うパターン
- 別に
http.ListenAndServe()
使えばええやんって話だけど、まあ物は試しで。
$ go run chap02/3.1/server_minmal.go
$ curl localhost
404 page not found
package main
import "net/http"
// http.ListenAndServe() じゃなくて http.Server を直接使う場合
func main() {
server := http.Server{Addr: "", Handler: nil}
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
Q. リスト3.4の実験をしたくね? 3.2.2全然わからん問題
- SSL証明書とかそのへんわかってない
-
cert.pem
の使い方とかもよくわからん - ので、雑にスキップ
code
package main
import "net/http"
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
// cert.pem: SSL証明書
// key.pem: 秘密鍵
err := server.ListenAndServeTLS("cert.pem", "key.pem")
if err != nil {
panic(err)
}
}
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
)
func main() {
max := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ := rand.Int(rand.Reader, max)
subject := pkix.Name{
Organization: []string{"Manning Publications Co."},
OrganizationalUnit: []string{"Books"},
CommonName: "Go Web Programming",
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
}
pk, _ := rsa.GenerateKey(rand.Reader, 2048)
derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &pk.PublicKey, pk)
certOut, _ := os.Create("cert.pem")
_ = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
_ = certOut.Close()
keyOut, _ := os.Create("key.pem")
_ = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
_ = keyOut.Close()
}
http
$ curl http://127.0.0.1:8080
Client sent an HTTP request to an HTTPS server.
$ http -v http://127.0.0.1:8080
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8080
User-Agent: HTTPie/2.4.0
HTTP/1.0 400 Bad Request
Client sent an HTTP request to an HTTPS server.
curl
とhttp
のエラーを見る
とりあえず$ curl https://127.0.0.1:8080
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
$ curl -E ./cert.pem https://127.0.0.1:8080
curl: (58) unable to set private key file: './cert.pem' type PEM
ノリでたたく
$ http --cert ./cert.pem https://127.0.0.1:8080
http: error: SSLError: HTTPSConnectionPool(host='127.0.0.1', port=8080): Max retries exceeded with url: / (Caused by SSLError(SSLError(9, '[SSL] PEM lib (_ssl.c:4048)'))) while doing a GET request to URL: https://127.0.0.1:8080/
$ http --cert-key ./cert.pem https://127.0.0.1:8080
http: error: SSLError: HTTPSConnectionPool(host='127.0.0.1', port=8080): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1123)'))) while doing a GET request to URL: https://127.0.0.1:8080/
ガン無視接続で強引につなぐ
$ curl -k https://127.0.0.1:8080
404 page not found
$ curl --insecure https://127.0.0.1:8080
404 page not found
パスのことを考慮しない感じになっているのおもろい
仕組みはこう。
URLのマッチングをしてないから、ぜーんぶ、MyHandler
に処理が流れるってだけ。
でも、1つのハンドラしか使えないのはあんまり役立たないよな。
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "Hello World!")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler,
}
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
$ curl 127.0.0.1:8080
Hello World!
$ curl 127.0.0.1:8080/foo/bar
Hello World!
$ curl 127.0.0.1:8080/anything/at/all
Hello World!
http.DefaultServeMux
がちゃんと動くわけ
そういえば、Handler
がnil
のときは、http.DefaultServeMux
になるわけですが、http.DefaultServeMux
は http.Hanlder
インタフェースを満たしていたわけでした
package main
import "net/http"
func main() {
_ = http.ListenAndServe("", nil) // nilなので http.DefaultServeMux になる
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
Q. ってか、「マルチプレクサ」ってなんだっけ?
なんだっけね?
1つのハンドラじゃあ大したことできないから、マルチプレクサにする。ハンドラ登録しようぜシステムを採用する
もし1つのハンドラでいろんなことやろうとしたら、ハンドラが膨れてつらいよね。そりゃそうだ。
用途に応じて、ハンドラをわけたいよね。
ってわけで、HandlerはDefaultServeMux
にしといて、そのうえで、ハンドラを登録するスタイルにする。
http.Handle
はHandler
を登録、http.HandleFunc
はハンドラ関数を登録する
http.Handle
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "Hello")
}
func main() {
hello := HelloHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
http.Handle("/hello", &hello)
server.ListenAndServe()
}
http.HandleFunc
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
http.HandleFunc("/hello", hello)
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "Hello")
}
HandlerFunc
と Handler
のややこしさ?
定義を捉えればいけると思う。
-
http.HandleFunc
は関数 -
http.HandlerFunc
はデータ型
ハンドラを呼び出すごとに、そのハンドラを呼び出す
- THE高階関数という感じ
- めちゃくちゃやりそうな処理
-
http.HandlerFunc
インタフェースを使うとスッキリ! (というかその存在を知っておくべきだね)
http.HandlerFunc
を使わないと、こういう定義になって非常に見づらい
func log(h func(w http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
// ...
}
code
package main
import (
"fmt"
"net/http"
"reflect"
"runtime"
)
func hello(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, "Hello!\n")
}
// 呼び出されたハンドラをロギングする
// http.HandlerFunc インタフェースの定義はコレ
// type HandlerFunc func(ResponseWriter, *Request)
func log(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// https://pkg.go.dev/runtime#FuncForPC
name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
fmt.Println("Handler function called - " + name)
h(w, r)
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil,
}
http.HandleFunc("/hello", log(hello))
_ = server.ListenAndServe()
}
runtime.FuncForPC
http.Handler
インタフェース
そういえば、- インタフェース名を覚えておくと、GoLandで有利
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
高階関数連打
- 関数も型なので、そのへんのなれが必要ですな
- 無名関数を返すのではなく、
http.HandlerFunc
型で包む感じ
package main
import (
"fmt"
"net/http"
)
// HelloHandler は http.Handler インターフェイスを満たしている
type HelloHandler struct{}
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, "Hello!")
}
// ハンドラの実行をロギングする
func log(h http.Handler) http.Handler {
// http.HandlerFunc型で包む
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Handler called - %T\n", h)
h.ServeHTTP(w, r)
})
}
// ハンドラを実行する前にユーザーの権限を確認する
func protect(h http.Handler) http.Handler {
// http.HandlerFunc型で包む
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ここで認証かなにかのコードを実行するとする
h.ServeHTTP(w, r)
})
}
func main() {
server := http.Server{Addr: "127.0.0.1:8080"}
hello := HelloHandler{}
http.Handle("/hello", protect(log(hello)))
_ = server.ListenAndServe()
}
やっぱ「マルチプレクサ」って何?
SercerMux
はHTTPリクエストのマルチプレクサ(多重通信の入口となるもの)です。
図3.5に示すように、HTTPリクエストを受け取って、リクエスト内のURLに応じて適切なハンドラに転送します。
ふむ?
もともとあるHTTPの考え方の話? それをGoではマルチプレクサって表現している?
なんとなくRouterっぽい感じがするけど、どうなんだろう?
先人の知恵
このへんの関数やデータ型やインタフェースを理解すれば見通しがかなり良くなりそう
http.Handler
http.HandlerFunc
http.HandleFunc
http.ServeMux
http.DefaultServeMux
http.ListenAndServe
名前も似ているしややこしや〜
マルチプレクサ(multiplexer)は、ルーターっぽい感じ。あるいはディスパッチャ。
ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.
(オレオレ訳)
ServerMux
はHTTPリクエストのマルチプレクサですよ。
とんできた各リクエストURLと登録済みのURLパターンと照合するよ。
そんでもって、URLをもっとも近いパターンのハンドラーを召喚するよ。
ServeMux
とDefaultServeMux
の違いを抑えよう
ひとことでいえば?
-
http.ServeMux
は構造体 -
http.DefaultServeMux
は単なるhppt.serveMux
型の変数(ポインタ)
// http/server.go
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
HandleFunc
で特に何も指定してないのに動く理由
今まで黙っていたけど、// こう書いてないのがちょっと不思議だった
http.HandleFunc(MyMux, "/hello", hello)
結局、HandleFunc
を呼び出していただけ!
んで、そんときにDefaultServeMux.HandleFunc
だからうまいこといくわけよ。
code
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: nil, // nilなので、 http.DefaultServeMux ってこと
}
http.HandleFunc("/hello", hello)
server.ListenAndServe()
}
そんで、http.HandleFunc()
の中身を読むと...
// HandleFuncを実行したら、結局DefaultServerMuxのHandleFuncが呼ばれている
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
っていうかね、HandleFunc
のDocにそのまま書いてあるよね!!!
HandleFunc registers the handler function for the given pattern in the DefaultServeMux.
Handler
として要件)は、ServeHTTP
を実装していればいいだけだから、いろんなマルチプレクサがあるよ
マルチプレクサの要件(≒インタフェースを理解すれば、するりと使えて便利。
HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go.
やっぱルーターだった。そして、マルチプレクサでもある。で、マルチプレクサの略称がmux
らしいね。
code
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintf(w, "hello, %s\n", p.ByName("name"))
}
func main() {
mux := httprouter.New()
mux.GET("/hello/:name", hello)
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: mux, // Handlerインタフェースは ServeHTTP 実装してりゃOK
}
server.ListenAndServe()
}
実行結果(予想通り)
$ curl localhost:8080/hello/Bob
hello, Bob
$ curl localhost:8080/hello/Tom
hello, Tom
$ curl localhost:8080/hello/
404 page not found
gorilla/mux
も有名らしい
マルチプレクサは
Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler.
こっちはディスパッチャって言っているね。
$ go get github.com/gorilla/mux
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello!")
}
func main() {
router := mux.NewRouter()
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: router,
}
router.HandleFunc("/hello", hello)
server.ListenAndServe()
}
ServeMux
を使ったパターンを先に抑えようねって感じだったわ
ってか、標準のpackage main
import (
"fmt"
"net/http"
)
func main() {
myMux := http.ServeMux{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &myMux,
}
myMux.HandleFunc("/hello", hello)
server.ListenAndServe()
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello!")
}
Handler
インタフェース(ServeHTTP()
)を実装した構造体を直接渡すパターンもあるんだった
そもそも、- 3.6でやった話だけど、いま見ると全然違うな
- というか、当時が全然わかってなかったという話
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func main() {
myHandler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &myHandler,
}
server.ListenAndServe()
}
HTTP2.0
package main
import (
"fmt"
"golang.org/x/net/http2"
"net/http"
)
type MyHandler struct{}
func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler,
}
http2.ConfigureServer(&server, &http2.Server{})
server.ListenAndServeTLS("cert.pem", "key.pem")
}
$ curl -I --insecure https://localhost:8080
HTTP/2 200
content-type: text/plain; charset=utf-8
content-length: 12
date: Fri, 02 Jul 2021 14:05:52 GMT
go doc
で調べると慣れが強化されそう
$ go doc net/http.HandleFunc
package http // import "net/http"
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc registers the handler function for the given pattern in the
DefaultServeMux. The documentation for ServeMux explains how patterns are
matched.
$ go doc net/http.HandlerFunc
package http // import "net/http"
type HandlerFunc func(ResponseWriter, *Request)
The HandlerFunc type is an adapter to allow the use of ordinary functions as
HTTP handlers. If f is a function with the appropriate signature,
HandlerFunc(f) is a Handler that calls f.
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
$ go doc net/http.Handle
package http // import "net/http"
func Handle(pattern string, handler Handler)
Handle registers the handler for the given pattern in the DefaultServeMux.
The documentation for ServeMux explains how patterns are matched.
第4章
リクエストヘッダを見よう
$ curl localhost:8080/headers -H "name:Bob" -H "age:25"
User-Agent [curl/7.64.1]
Accept [*/*]
Name [Bob]
Age [25]
$ http localhost:8080/headers name:Bob age:25
HTTP/1.1 200 OK
Content-Length: 115
Content-Type: text/plain; charset=utf-8
Date: Sat, 03 Jul 2021 14:23:13 GMT
Connection [keep-alive]
Name [Bob]
Age [25]
User-Agent [HTTPie/2.4.0]
Accept-Encoding [gzip, deflate]
Accept [*/*]
package main
import (
"fmt"
"net/http"
)
func headers(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
fmt.Fprintf(w, "%s %s\n", k,v)
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/headers", headers)
server.ListenAndServe()
}
net/http.Request.Form
と net/http.Request.PostForm
一言で、URLクエリパラメータもセットで取るかどうかだよ。
$ go doc net/http.request.Form
package http // import "net/http"
type Request struct {
// Form contains the parsed form data, including both the URL field's query
// parameters and the PATCH, POST, or PUT form data. This field is only
// available after ParseForm is called. The HTTP client ignores Form and uses
// Body instead.
Form url.Values
// ... other fields elided ...
}
Form contains the parsed form data, including both the URL field's query
parameters and the PATCH, POST, or PUT form data.
URLのクエリパラメータも取ってこれるよ〜。
This field is only available after ParseForm is called.
Request.ParseForm()
を先に呼ばないとアカンぜよ。
The HTTP client ignores Form and uses Body instead.
$ go doc net/http.request.PostForm
package http // import "net/http"
type Request struct {
// PostForm contains the parsed form data from PATCH, POST or PUT body
// parameters.
//
// This field is only available after ParseForm is called. The HTTP client
// ignores PostForm and uses Body instead.
PostForm url.Values
// ... other fields elided ...
}
PostForm contains the parsed form data from PATCH, POST or PUT body
parameters.
PostForm
はForm
と違って、URLのクエリパラメータは取らないよ〜。
This field is only available after ParseForm is called. The HTTP client
こっちは、同じやつ。
ignores PostForm and uses Body instead.
Request.ParseForm
しないとこうなるよ
Request.ParseForm
の解説
$ go doc net/http.request.ParseForm
package http // import "net/http"
func (r *Request) ParseForm() error
ParseForm populates r.Form and r.PostForm.
For all requests, ParseForm parses the raw query from the URL and updates
r.Form.
For POST, PUT, and PATCH requests, it also reads the request body, parses it
as a form and puts the results into both r.PostForm and r.Form. Request body
parameters take precedence over URL query string values in r.Form.
If the request Body's size has not already been limited by MaxBytesReader,
the size is capped at 10MB.
For other HTTP methods, or when the Content-Type is not
application/x-www-form-urlencoded, the request Body is not read, and
r.PostForm is initialized to a non-nil, empty value.
ParseMultipartForm calls ParseForm automatically. ParseForm is idempotent.
ParseForm populates r.Form and r.PostForm.
r.Form
とr.PostForm
にデータを流し込むよ。
For all requests, ParseForm parses the raw query from the URL and updates
r.Form.
すべてのリクエストに対して、URLクエリパラメータを解析して、r.Form
を更新する(値をセットかな?)
For POST, PUT, and PATCH requests, it also reads the request body, parses it
as a form and puts the results into both r.PostForm and r.Form.
POST, PUT, PATCHリクエストの場合、リクエストボディも読むよ。
そんで、r.PostForm
とr.Form
に値をセットする感じ。
Request body parameters take precedence over URL query string values in r.Form.
リクエストボディは、URLのクエリストリングよりも優先されてr.Form
にセットされるよ。
要は、クエリパラメータと、リクエストボディで、キーがかぶった場合はリクエストボディを優先するってこと。
<!-- クエリパラメータにも name -->
<form action="http://127.0.0.1:8080/process?username=Bob" method="POST">
<!-- POSTのBodyにも name -->
<input type="text" name="username" value="Ken"/>
<input type="submit"/>
</form>
If the request Body's size has not already been limited by MaxBytesReader,the size is capped at 10MB.
限界あるよ。へ〜。
For other HTTP methods, or when the Content-Type is not
application/x-www-form-urlencoded, the request Body is not read, and
r.PostForm is initialized to a non-nil, empty value.
他のHTTPメソッド(POST, PUT, PATCH以外)あるいは、Content-Type
がapplication/x-www-form-urlencoded
の場合、リクエストボディは読まれないよ。
そんで、r.Postform
はnil
じゃない、空の値に初期化されるよ。
ParseMultipartForm calls ParseForm automatically. ParseForm is idempotent.
ParseMultipartForm
は、ParseForm
を自動的に呼ぶよ。で、ParseForm
は冪等だよ。
4.2.3のMultipartFormの実験はスキップした
コードを書くのだるかってん。
Q. POSTリクエストのエンコード方式は一回調べるといいかも?
『Real World HTTP』の2.2あたりとか
http.ResponseWriter.WriteHeader
の話
http.ResponseWriter.WriteHeader
は、そもそもhttp.ResponseWriter
インタフェースのメソッドという話を思い出すところから
$ go doc net/http.ResponseWriter | grep -v '//'
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
A ResponseWriter interface is used by an HTTP handler to construct an HTTP
response.
A ResponseWriter may not be used after the Handler.ServeHTTP method has
returned.
WriteHeader sends an HTTP response header with the provided
status code.
レスポンスヘッダにステータスコードを与えるやーつ。
If WriteHeader is not called explicitly, the first call to Write
will trigger an implicit WriteHeader(http.StatusOK).
明示的にWriteHeader
が呼ばれない場合は、勝手にhttp.StatusOK
(要は、200
な)をしてくれる。これは、Write()
が呼ばれたことをトリガーとして、暗黙的に呼ばれるわけな。
Thus explicit calls to WriteHeader are mainly used to
send error codes.
勝手に呼ばれるわけですが、逆に言うと、明示的に使うのはどういうとき? って話がありますな。それは「エラーコード」(というか、エラーを表現するステータスコード)を贈りたいとき。
The provided code must be a valid HTTP 1xx-5xx status code.
ってか、ステータスコードのvalidってどれよ?
これは、panic。
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(123456789) // http: panic serving 127.0.0.1:52373: invalid WriteHeader code 123456789
}
これも、panic。
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(0) // http: panic serving 127.0.0.1:52373: invalid WriteHeader code 0
}
でも、999
はセーフ。
func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(999)
}
Only one header may be written. Go does not currently
support sending user-defined 1xx informational headers,
with the exception of 100-continue response header that the
Server sends automatically when the Request.Body is read.
なんやようわからん
net/http.response.Response
な
ところで、具象データ型は、で、全然わからんけど、
func (w *response) WriteHeader(code int) {
if w.conn.hijacked() {
caller := relevantCaller()
w.conn.server.logf("http: response.WriteHeader on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
// ここで、 superfluous のエラーメッセージがあるやーつ
if w.wroteHeader {
caller := relevantCaller()
w.conn.server.logf("http: superfluous response.WriteHeader call from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
checkWriteHeaderCode(code)
w.wroteHeader = true
w.status = code
if w.calledHeader && w.cw.header == nil {
w.cw.header = w.handlerHeader.Clone()
}
if cl := w.handlerHeader.get("Content-Length"); cl != "" {
v, err := strconv.ParseInt(cl, 10, 64)
if err == nil && v >= 0 {
w.contentLength = v
} else {
w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
}
$ go doc -u net/http.response|grep -v '//' |pbcopy
type Response struct {
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Uncompressed bool
Trailer Header
Request *Request
TLS *tls.ConnectionState
}
Response represents the response from an HTTP request.
The Client and Transport return Responses from servers once the response
headers have been received. The response body is streamed on demand as the
Body field is read.
func (r *Response) Cookies() []*Cookie
func (r *Response) Location() (*url.URL, error)
func (r *Response) ProtoAtLeast(major, minor int) bool
func (r *Response) Write(w io.Writer) error
func (r *Response) bodyIsWritable() bool
func (r *Response) closeBody()
func (r *Response) isProtocolSwitch() bool
type response struct {
conn *conn
reqBody io.ReadCloser
canWriteContinue atomicBool
writeContinueMu sync.Mutex
cw chunkWriter
handlerHeader Header
closeAfterReply bool
requestBodyLimitHit bool
trailers []string
dateBuf [len(TimeFormat)]byte
clenBuf [10]byte
statusBuf [3]byte
closeNotifyCh chan bool
}
A response represents the server side of an HTTP response.
func (w *response) CloseNotify() <-chan bool
func (w *response) Flush()
func (w *response) Header() Header
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)
func (w *response) ReadFrom(src io.Reader) (n int64, err error)
func (w *response) Write(data []byte) (n int, err error)
func (w *response) WriteHeader(code int)
func (w *response) WriteString(data string) (n int, err error)
func (w *response) bodyAllowed() bool
func (w *response) closedRequestBodyEarly() bool
func (w *response) declareTrailer(k string)
func (w *response) finalTrailers() Header
func (w *response) finishRequest()
func (w *response) needsSniff() bool
func (w *response) requestTooLarge()
func (w *response) sendExpectationFailed()
func (w *response) shouldReuseConnection() bool
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error)
これだけでJSONサーバー書けるのめっちゃ楽なのでは!?
なんとなくnet/http
のすごさを感じた。これが世にいうnet/http
の素晴らしさなのか?
package main
import (
"encoding/json"
"log"
"net/http"
)
type Post struct {
User string
Threads []string
}
func jsonExample(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "Bob",
Threads: []string{"1番目", "2番目", "3番目"},
}
jsonBytes, err := json.Marshal(post)
if err != nil {
log.Println(err)
}
_, _ = w.Write(jsonBytes)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/json", jsonExample)
server.ListenAndServe()
}
http.SetCookie
の実験
$ go doc net/http.SetCookie
package http // import "net/http"
func SetCookie(w ResponseWriter, cookie *Cookie)
SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
The provided cookie must have a valid Name. Invalid cookies may be silently dropped.
The provided cookie must have a valid Name. Invalid cookies may be silently dropped
名前がないと、勝手に落ちるよ。まあ、「落ちる」っていうか、なかったことになるって感じ。
というわけで、試してみた。
package main
import (
"net/http"
)
func setCookieHandler(w http.ResponseWriter, r *http.Request) {
validCookie := http.Cookie{
Name: "first_cookie",
Value: "Go Web Programming",
HttpOnly: true,
}
http.SetCookie(w, &validCookie)
invalidCookie := http.Cookie{
Value: "Nameがないと",
}
http.SetCookie(w, &invalidCookie)
}
func main() {
server := http.Server{Addr: "127.0.0.1:8080"}
http.HandleFunc("/set_cookie", setCookieHandler)
server.ListenAndServe()
}
// Set-Cookieヘッダは1つしかない!!!
$ curl -i localhost:8080/set_cookie
HTTP/1.1 200 OK
Set-Cookie: first_cookie="Go Web Programming"; HttpOnly
Date: Sun, 04 Jul 2021 14:36:43 GMT
Content-Length: 0
http.SetCookie
の実装を見れば、その仕組もわかる
- 空文字じゃなかったら(
Stringer
の実装はややこしかったのでスルーした)、Set-Cookie
ヘッダに書き込む - 文字列評価が空だったら、何もしない!!!
$ go doc -src net/http.SetCookie
package http // import "net/http"
// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func SetCookie(w ResponseWriter, cookie *Cookie) {
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v)
}
}
クッキーの取得
素でCookieを取得する
- 素直にヘッダーに書き込む
- 文字列スライスなのでちょっと不便
func getCookieHandler(w http.ResponseWriter, r *http.Request) {
h := r.Header["Cookie"]
fmt.Fprintln(w, h)
// 単なる文字列スライスなので操作には不便
fmt.Fprintf(w,"%T\n",h ) // []string
}
http.Request.Cookie
だと楽にとれるね
- 実装もVery素直
$ go doc -src http.Request.Cookie
package http // import "net/http"
// Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found.
// If multiple cookies match the given name, only one cookie will
// be returned.
func (r *Request) Cookie(name string) (*Cookie, error) {
for _, c := range readCookies(r.Header, name) {
return c, nil
}
return nil, ErrNoCookie
}
$ go doc -src http.ErrNoCookie
package http // import "net/http"
// ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
var ErrNoCookie = errors.New("http: named cookie not present")
http.Request.Cookies
だと、*Cookie
のスライスとれるので、これも便利
$ go doc http.Request.Cookies
package http // import "net/http"
func (r *Request) Cookies() []*Cookie
Cookies parses and returns the HTTP cookies sent with the request.
http.SetCookie
の前に、Fprintln
を実行するとクッキーが保存されないの何で?
未解決: なにかミスっているか?
package main
import (
"fmt"
"log"
"net/http"
)
func nazoCookieHandler(w http.ResponseWriter, r *http.Request) {
// 少なくとも fmt.Fprintln() するとCookieがセットされない
// 他の原因もありそう
fmt.Fprintln(w, "何かが原因でCookieがセットされない")
c := http.Cookie{
Name: "name",
Value: "Bob",
}
http.SetCookie(w, &c)
}
func kakuninCookieHandler(w http.ResponseWriter, r *http.Request) {
c2, err := r.Cookie("name")
if err != nil {
fmt.Fprintln(w, err)
} else {
fmt.Fprintln(w, c2)
}
}
func main() {
server := http.Server{Addr: "127.0.0.1:8080"}
http.HandleFunc("/nazo", nazoCookieHandler)
http.HandleFunc("/kakunin", kakuninCookieHandler)
err := server.ListenAndServe()
if err != nil {
log.Println(err)
}
}
第5章
テンプレートエンジンはなんかテンション上がらないので、ひとまずスルー
第6章
- 6.2まで
- メモリで管理
- 構造体とかmapとか
- ファイルで読み書き(
io.Writer
とio.Reader
という感じ) -
encoding/csv
の使い方 -
encoding/gob
の使い方
- メモリで管理
ioutil.Writefie
じゃなくてos.Writefile
を使おうな
$ go doc ioutil.Writefile
package ioutil // import "io/ioutil"
func WriteFile(filename string, data []byte, perm fs.FileMode) error
WriteFile writes data to a file named by filename. If the file does not
exist, WriteFile creates it with permissions perm (before umask); otherwise
WriteFile truncates it before writing, without changing permissions.
As of Go 1.16, this function simply calls os.WriteFile.
$ go doc os.Writefile
package os // import "os"
func WriteFile(name string, data []byte, perm FileMode) error
WriteFile writes data to the named file, creating it if necessary. If the
file does not exist, WriteFile creates it with permissions perm (before
umask); otherwise WriteFile truncates it before writing, without changing
permissions.
ioutil.Readfile
じゃなくて、os.Readfile
を使おうな
$ go doc ioutil.Readfile
package ioutil // import "io/ioutil"
func ReadFile(filename string) ([]byte, error)
ReadFile reads the file named by filename and returns the contents. A
successful call returns err == nil, not err == EOF. Because ReadFile reads
the whole file, it does not treat an EOF from Read as an error to be
reported.
As of Go 1.16, this function simply calls os.ReadFile.
$ go doc os.Readfile
package os // import "os"
func ReadFile(name string) ([]byte, error)
ReadFile reads the named file and returns the contents. A successful call
returns err == nil, not err == EOF. Because ReadFile reads the whole file,
it does not treat an EOF from Read as an error to be reported.
encoding/gob
とは?
ごぶ?
bytes.Buffer
はnew
関数使わず、ゼロ値でよくない?
$ go doc bytes.Buffer
package bytes // import "bytes"
type Buffer struct {
// Has unexported fields.
}
A Buffer is a variable-sized buffer of bytes with Read and Write methods.
The zero value for Buffer is an empty buffer ready to use.
init()
関数
RETURNING
句
Named Return Value
の書き方に慣れないね。
Kindle版のリスト6.6は、typoあるっぽい
- 書籍はなおってた
// 誤: 引数がない!
posts, err := Posts()
posts, err := Posts(10)